Compare commits

..

15 Commits
float ... decor

Author SHA1 Message Date
Kovid Goyal
139f56b2eb Port the code to set window size limits to wl_decor 2024-04-03 18:55:27 +05:30
Kovid Goyal
84ef1dce92 Migrate setting of title and app id to wl_decor 2024-04-03 18:55:27 +05:30
Kovid Goyal
14c0d76f86 Port set minimized to wl_decor 2024-04-03 18:55:27 +05:30
Kovid Goyal
fbc2b4fc1c Migrate maximized state management to wl_decor 2024-04-03 18:55:27 +05:30
Kovid Goyal
d557d4cbec Migrate setting of fullscreen to wl_decor 2024-04-03 18:55:27 +05:30
Kovid Goyal
16b5948c95 ... 2024-04-03 18:55:26 +05:30
Kovid Goyal
4e9e833397 Only initialize edge_spacing_func if glfw init succeeds 2024-04-03 18:55:26 +05:30
Kovid Goyal
1f15002ace Set GDK_BACKEND if needed before loading libdecor 2024-04-03 18:55:26 +05:30
Kovid Goyal
886ca2d0e6 Store library handle in funcs struct 2024-04-03 18:55:26 +05:30
Kovid Goyal
45f3577f35 O_CLOEXEC for linux joystick open 2024-04-03 18:55:26 +05:30
Kovid Goyal
8b66c3faa4 Propagate failures to get video mode 2024-04-03 18:55:26 +05:30
Kovid Goyal
5346c95ff4 Integrate libdecor into event loop 2024-04-03 18:55:26 +05:30
Kovid Goyal
aecacb0295 Note that file transfer wont work through tmux in the FAQ 2024-04-03 18:55:26 +05:30
Kovid Goyal
34ddcaaa57 Create decoration context 2024-04-03 18:55:26 +05:30
Kovid Goyal
92253521b1 dlopen libdecor 2024-04-03 18:55:26 +05:30
507 changed files with 25328 additions and 60629 deletions

View File

@@ -11,7 +11,7 @@ indent_style = tab
# Autogenerated files with tabs below this line. # Autogenerated files with tabs below this line.
[kitty/char-props-data.h] [kitty/{unicode-data.c,emoji.h,wcwidth-std.h}]
indent_style = tab indent_style = tab
[kittens/unicode_input/names.h] [kittens/unicode_input/names.h]

8
.gitattributes vendored
View File

@@ -1,14 +1,14 @@
kitty/char-props-data.h linguist-generated=true kitty/wcwidth-std.h linguist-generated=true
kitty_tests/GraphemeBreakTest.json linguist-generated=true kitty/emoji.h linguist-generated=true
kitty/charsets.c linguist-generated=true kitty/charsets.c linguist-generated=true
kitty/key_encoding.py linguist-generated=true kitty/key_encoding.py linguist-generated=true
kitty/unicode-data.c linguist-generated=true
kitty/rowcolumn-diacritics.c linguist-generated=true kitty/rowcolumn-diacritics.c linguist-generated=true
kitty/rgb.py linguist-generated=true kitty/rgb.py linguist-generated=true
kitty/srgb_gamma.* linguist-generated=true kitty/srgb_gamma.* linguist-generated=true
kitty/gl-wrapper.* linguist-generated=true kitty/gl-wrapper.* linguist-generated=true
kitty/glfw-wrapper.* linguist-generated=true kitty/glfw-wrapper.* linguist-generated=true
kitty/parse-graphics-command.h linguist-generated=true kitty/parse-graphics-command.h linguist-generated=true
kitty/parse-multicell-command.h linguist-generated=true
kitty/options/types.py linguist-generated=true kitty/options/types.py linguist-generated=true
kitty/options/parse.py linguist-generated=true kitty/options/parse.py linguist-generated=true
kitty/options/to-c-generated.h linguist-generated=true kitty/options/to-c-generated.h linguist-generated=true
@@ -18,7 +18,7 @@ glfw/*.c linguist-vendored=true
glfw/*.h linguist-vendored=true glfw/*.h linguist-vendored=true
3rdparty/** linguist-vendored=true 3rdparty/** linguist-vendored=true
kittens/unicode_input/names.h linguist-generated=true kittens/unicode_input/names.h linguist-generated=true
tools/wcswidth/char-props-data.go linguist-generated=true tools/wcswidth/std.go linguist-generated=true
tools/unicode_names/names.txt linguist-generated=true tools/unicode_names/names.txt linguist-generated=true
terminfo/kitty.term* linguist-generated=true terminfo/kitty.term* linguist-generated=true
terminfo/x/* linguist-generated=true terminfo/x/* linguist-generated=true

View File

@@ -23,7 +23,7 @@ If applicable, add screenshots to help explain your problem.
**Environment details** **Environment details**
``` ```
Press Ctrl+Shift+F6 (cmd+option+comma on macOS) in kitty, to copy debug output about kitty and its Press Ctrl+Shift+F6 (cmd+option+comma on macOS) in kitty, to copy debug output about kitty and its
configuration to the clipboard and paste it here. configuration to the clipboard and paste it here.
On older versions of kitty, run kitty --debug-config instead On older versions of kitty, run kitty --debug-config instead
``` ```

View File

@@ -14,14 +14,12 @@ import time
from urllib.request import urlopen from urllib.request import urlopen
BUNDLE_URL = 'https://download.calibre-ebook.com/ci/kitty/{}-64.tar.xz' BUNDLE_URL = 'https://download.calibre-ebook.com/ci/kitty/{}-64.tar.xz'
FONTS_URL = 'https://download.calibre-ebook.com/ci/fonts.tar.xz'
NERD_URL = 'https://github.com/ryanoasis/nerd-fonts/releases/latest/download/NerdFontsSymbolsOnly.tar.xz'
is_bundle = os.environ.get('KITTY_BUNDLE') == '1' is_bundle = os.environ.get('KITTY_BUNDLE') == '1'
is_macos = 'darwin' in sys.platform.lower() is_macos = 'darwin' in sys.platform.lower()
SW = '' SW = None
def do_print_crash_reports() -> None: def do_print_crash_reports():
print('Printing available crash reports...') print('Printing available crash reports...')
if is_macos: if is_macos:
end_time = time.monotonic() + 90 end_time = time.monotonic() + 90
@@ -40,9 +38,9 @@ def do_print_crash_reports() -> None:
print(flush=True) print(flush=True)
def run(*a: str, print_crash_reports: bool = False) -> None: def run(*a, print_crash_reports=False):
if len(a) == 1: if len(a) == 1:
a = tuple(shlex.split(a[0])) a = shlex.split(a[0])
cmd = ' '.join(map(shlex.quote, a)) cmd = ' '.join(map(shlex.quote, a))
print(cmd) print(cmd)
sys.stdout.flush() sys.stdout.flush()
@@ -61,26 +59,7 @@ def run(*a: str, print_crash_reports: bool = False) -> None:
raise SystemExit(f'The following process failed with exit code: {ret}:\n{cmd}') raise SystemExit(f'The following process failed with exit code: {ret}:\n{cmd}')
def install_fonts() -> None: def install_deps():
with urlopen(FONTS_URL) as f:
data = f.read()
fonts_dir = os.path.expanduser('~/Library/Fonts' if is_macos else '~/.local/share/fonts')
os.makedirs(fonts_dir, exist_ok=True)
with tarfile.open(fileobj=io.BytesIO(data), mode='r:xz') as tf:
try:
tf.extractall(fonts_dir, filter='fully_trusted')
except TypeError:
tf.extractall(fonts_dir)
with urlopen(NERD_URL) as f:
data = f.read()
with tarfile.open(fileobj=io.BytesIO(data), mode='r:xz') as tf:
try:
tf.extractall(fonts_dir, filter='fully_trusted')
except TypeError:
tf.extractall(fonts_dir)
def install_deps() -> None:
print('Installing kitty dependencies...') print('Installing kitty dependencies...')
sys.stdout.flush() sys.stdout.flush()
if is_macos: if is_macos:
@@ -90,13 +69,13 @@ def install_deps() -> None:
import ssl import ssl
if ssl.OPENSSL_VERSION_INFO[0] == 1: if ssl.OPENSSL_VERSION_INFO[0] == 1:
openssl += '@1.1' openssl += '@1.1'
run('brew', 'install', 'fish', openssl, *items) run('brew', 'install', 'fish', 'simde', openssl, *items)
else: else:
run('sudo apt-get update') run('sudo apt-get update')
run('sudo apt-get install -y libgl1-mesa-dev libxi-dev libxrandr-dev libxinerama-dev ca-certificates' run('sudo apt-get install -y libgl1-mesa-dev libxi-dev libxrandr-dev libxinerama-dev ca-certificates'
' libxcursor-dev libxcb-xkb-dev libdbus-1-dev libxkbcommon-dev libharfbuzz-dev libx11-xcb-dev zsh' ' libxcursor-dev libxcb-xkb-dev libdbus-1-dev libxkbcommon-dev libharfbuzz-dev libx11-xcb-dev zsh'
' libpng-dev liblcms2-dev libfontconfig-dev libxkbcommon-x11-dev libcanberra-dev libxxhash-dev uuid-dev' ' libpng-dev liblcms2-dev libfontconfig-dev libxkbcommon-x11-dev libcanberra-dev libxxhash-dev uuid-dev'
' libsimde-dev libsystemd-dev libcairo2-dev zsh bash dash systemd-coredump gdb') ' libsimde-dev zsh bash dash systemd-coredump gdb')
# for some reason these directories are world writable which causes zsh # for some reason these directories are world writable which causes zsh
# compinit to break # compinit to break
run('sudo chmod -R og-w /usr/share/zsh') run('sudo chmod -R og-w /usr/share/zsh')
@@ -107,10 +86,9 @@ def install_deps() -> None:
if sys.version_info[:2] < (3, 7): if sys.version_info[:2] < (3, 7):
cmd += ' importlib-resources dataclasses' cmd += ' importlib-resources dataclasses'
run(cmd) run(cmd)
install_fonts()
def build_kitty() -> None: def build_kitty():
python = shutil.which('python3') if is_bundle else sys.executable python = shutil.which('python3') if is_bundle else sys.executable
cmd = f'{python} setup.py build --verbose' cmd = f'{python} setup.py build --verbose'
if is_macos: if is_macos:
@@ -120,14 +98,14 @@ def build_kitty() -> None:
run(cmd) run(cmd)
def test_kitty() -> None: def test_kitty():
if is_macos: if is_macos:
run('ulimit -c unlimited') run('ulimit -c unlimited')
run('sudo chmod -R 777 /cores') run('sudo chmod -R 777 /cores')
run('./test.py', print_crash_reports=True) run('./test.py', print_crash_reports=True)
def package_kitty() -> None: def package_kitty():
python = 'python3' if is_macos else 'python' python = 'python3' if is_macos else 'python'
run(f'{python} setup.py linux-package --update-check-interval=0 --verbose') run(f'{python} setup.py linux-package --update-check-interval=0 --verbose')
if is_macos: if is_macos:
@@ -135,14 +113,14 @@ def package_kitty() -> None:
run('kitty.app/Contents/MacOS/kitty +runpy "from kitty.constants import *; print(kitty_exe())"') run('kitty.app/Contents/MacOS/kitty +runpy "from kitty.constants import *; print(kitty_exe())"')
def replace_in_file(path: str, src: str, dest: str) -> None: def replace_in_file(path, src, dest):
with open(path, 'r+') as f: with open(path, 'r+') as f:
n = f.read().replace(src, dest) n = f.read().replace(src, dest)
f.seek(0), f.truncate() f.seek(0), f.truncate()
f.write(n) f.write(n)
def setup_bundle_env() -> None: def setup_bundle_env():
global SW global SW
os.environ['SW'] = SW = '/Users/Shared/kitty-build/sw/sw' if is_macos else os.path.join(os.environ['GITHUB_WORKSPACE'], 'sw') os.environ['SW'] = SW = '/Users/Shared/kitty-build/sw/sw' if is_macos else os.path.join(os.environ['GITHUB_WORKSPACE'], 'sw')
os.environ['PKG_CONFIG_PATH'] = os.path.join(SW, 'lib', 'pkgconfig') os.environ['PKG_CONFIG_PATH'] = os.path.join(SW, 'lib', 'pkgconfig')
@@ -154,17 +132,14 @@ def setup_bundle_env() -> None:
os.environ['PATH'] = '{}:{}'.format(os.path.join(SW, 'bin'), os.environ['PATH']) os.environ['PATH'] = '{}:{}'.format(os.path.join(SW, 'bin'), os.environ['PATH'])
def install_bundle() -> None: def install_bundle():
cwd = os.getcwd() cwd = os.getcwd()
os.makedirs(SW) os.makedirs(SW)
os.chdir(SW) os.chdir(SW)
with urlopen(BUNDLE_URL.format('macos' if is_macos else 'linux')) as f: with urlopen(BUNDLE_URL.format('macos' if is_macos else 'linux')) as f:
data = f.read() data = f.read()
with tarfile.open(fileobj=io.BytesIO(data), mode='r:xz') as tf: with tarfile.open(fileobj=io.BytesIO(data), mode='r:xz') as tf:
try: tf.extractall()
tf.extractall(filter='fully_trusted')
except TypeError:
tf.extractall()
if not is_macos: if not is_macos:
replaced = 0 replaced = 0
for dirpath, dirnames, filenames in os.walk('.'): for dirpath, dirnames, filenames in os.walk('.'):
@@ -177,7 +152,7 @@ def install_bundle() -> None:
os.chdir(cwd) os.chdir(cwd)
def main() -> None: def main():
if is_bundle: if is_bundle:
setup_bundle_env() setup_bundle_env()
else: else:
@@ -193,9 +168,9 @@ def main() -> None:
elif action == 'test': elif action == 'test':
test_kitty() test_kitty()
elif action == 'gofmt': elif action == 'gofmt':
q = subprocess.check_output('gofmt -s -l tools kittens'.split()).decode() q = subprocess.check_output('gofmt -s -l tools'.split())
if q.strip(): if q.strip():
q = '\n'.join(filter(lambda x: not x.rstrip().endswith('_generated.go'), q.strip().splitlines())).strip() q = '\n'.join(filter(lambda x: not x.rstrip().endswith('_generated.go'), q.decode().strip().splitlines())).strip()
if q: if q:
raise SystemExit(q) raise SystemExit(q)
else: else:

View File

@@ -23,7 +23,7 @@ jobs:
cc: [gcc, clang] cc: [gcc, clang]
include: include:
- python: a - python: a
pyver: "3.10" pyver: "3.8"
sanitize: 0 sanitize: 0
- python: b - python: b
@@ -31,7 +31,7 @@ jobs:
sanitize: 1 sanitize: 1
- python: c - python: c
pyver: "3.12" pyver: "3.9"
sanitize: 1 sanitize: 1
@@ -48,7 +48,6 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
fetch-depth: 10 fetch-depth: 10
persist-credentials: false
- name: Set up Python ${{ matrix.pyver }} - name: Set up Python ${{ matrix.pyver }}
uses: actions/setup-python@v4 uses: actions/setup-python@v4
@@ -76,18 +75,14 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
fetch-depth: 0 # needed for :commit: docs role fetch-depth: 0 # needed for :commit: docs role
persist-credentials: false
- name: Test for trailing whitespace - name: Test for trailing whitespace
run: if grep -Inr '\s$' kitty kitty_tests kittens docs *.py *.asciidoc *.rst *.go .gitattributes .gitignore; then echo Trailing whitespace found, aborting.; exit 1; fi run: if grep -Inr '\s$' kitty kitty_tests kittens docs *.py *.asciidoc *.rst *.go .gitattributes .gitignore; then echo Trailing whitespace found, aborting.; exit 1; fi
- name: Test for bad code block formatting
run: if grep -Inr ':code:`\s' kitty kitty_tests kittens docs *.py *.asciidoc *.rst *.go .gitattributes .gitignore; then echo Space at code block start found, aborting.; exit 1; fi
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
python-version: "3.13" python-version: "3.10"
- name: Install Go - name: Install Go
uses: actions/setup-go@v4 uses: actions/setup-go@v4
@@ -109,7 +104,7 @@ jobs:
run: python -m pip install -r docs/requirements.txt ruff mypy types-requests types-docutils run: python -m pip install -r docs/requirements.txt ruff mypy types-requests types-docutils
- name: Run ruff - name: Run ruff
run: ruff check . run: ruff .
- name: Run gofmt - name: Run gofmt
run: go version && python .github/workflows/ci.py gofmt run: go version && python .github/workflows/ci.py gofmt
@@ -124,7 +119,7 @@ jobs:
run: which python && python -m mypy --version && ./test.py mypy run: which python && python -m mypy --version && ./test.py mypy
- name: Run go vet - name: Run go vet
run: go version && go vet -tags testing ./... run: go version && go vet ./...
- name: Build man page - name: Build man page
run: make FAIL_WARN=1 man run: make FAIL_WARN=1 man
@@ -148,7 +143,6 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
fetch-depth: 10 fetch-depth: 10
persist-credentials: false
- name: Install Go - name: Install Go
uses: actions/setup-go@v4 uses: actions/setup-go@v4
@@ -169,12 +163,11 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
fetch-depth: 0 # needed for :commit: docs role fetch-depth: 0 # needed for :commit: docs role
persist-credentials: false
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
python-version: "3.11" python-version: "3.10"
- name: Install Go - name: Install Go
uses: actions/setup-go@v4 uses: actions/setup-go@v4
@@ -195,30 +188,3 @@ jobs:
- name: Build kitty package - name: Build kitty package
run: python3 .github/workflows/ci.py package run: python3 .github/workflows/ci.py package
- name: Run benchmarks
run: ./benchmark.py
linux-dev:
name: Test ./dev.sh and benchmark
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@v3
with:
fetch-depth: 10
persist-credentials: false
- name: Install build deps
run: sudo apt-get update && sudo apt-get install -y curl xz-utils build-essential git pkg-config libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libgl1-mesa-dev libxkbcommon-x11-dev libfontconfig-dev libx11-xcb-dev libdbus-1-dev
- name: Install Go
uses: actions/setup-go@v4
with:
go-version-file: go.mod
- name: Build kitty
run: ./dev.sh build
- name: Run benchmarks
run: ./benchmark.py

View File

@@ -1,49 +1,49 @@
name: "Code scanning - action" name: "Code scanning - action"
on: on:
push: push:
branches: [master, ] branches: [master, ]
pull_request: pull_request:
# The branches below must be a subset of the branches above # The branches below must be a subset of the branches above
branches: [master] branches: [master]
schedule: schedule:
- cron: '0 22 * * 5' - cron: '0 22 * * 5'
permissions: permissions:
contents: read # to fetch code (actions/checkout) contents: read # to fetch code (actions/checkout)
jobs: jobs:
CodeQL-Build: CodeQL-Build:
permissions: permissions:
contents: read # to fetch code (actions/checkout) contents: read # to fetch code (actions/checkout)
security-events: write # to upload SARIF results (github/codeql-action/analyze) security-events: write # to upload SARIF results (github/codeql-action/analyze)
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
# We must fetch at least the immediate parents so that if this is # We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head. # a pull request then we can checkout the head.
fetch-depth: 2 fetch-depth: 2
- name: Install Go - name: Install Go
uses: actions/setup-go@v3 uses: actions/setup-go@v3
with: with:
go-version-file: go.mod go-version-file: go.mod
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v3 uses: github/codeql-action/init@v2
with: with:
languages: python, c languages: python, c
setup-python-dependencies: false setup-python-dependencies: false
- name: Build kitty - name: Build kitty
run: python3 .github/workflows/ci.py build run: python3 .github/workflows/ci.py build
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3 uses: github/codeql-action/analyze@v2

1
.gitignore vendored
View File

@@ -12,7 +12,6 @@
/dependencies /dependencies
/tags /tags
/build/ /build/
/fonts/
/linux-package/ /linux-package/
/kitty.app/ /kitty.app/
/glad/out/ /glad/out/

View File

@@ -176,7 +176,7 @@ CC=clang CFLAGS="--target=aarch64-linux-gnu -march=armv8-a" NEON64_CFLAGS=" " ma
### OpenMP ### OpenMP
To enable OpenMP on GCC you need to build with `-fopenmp`. This can be by setting the `OPENMP` environment variable to `1`. To enable OpenMP on GCC you need to build with `-fopenmp`. This can be by setting the the `OPENMP` environment variable to `1`.
Example: Example:

1140
3rdparty/uthash.h vendored Normal file

File diff suppressed because it is too large Load Diff

1946
3rdparty/verstable.h vendored

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
brew "pkg-config"
brew "zlib" brew "zlib"
brew "xxhash" brew "xxhash"
brew "simde"
brew "python" brew "python"
brew "imagemagick" brew "imagemagick"
brew "harfbuzz" brew "harfbuzz"

View File

@@ -11,4 +11,4 @@ https://www.reddit.com/r/KittyTerminal[Reddit community]
Packaging status in various repositories: Packaging status in various repositories:
image:https://repology.org/badge/vertical-allrepos/kitty-terminal.svg?columns=3&header=kitty["Packaging status", link="https://repology.org/project/kitty-terminal/versions"] image:https://repology.org/badge/vertical-allrepos/kitty.svg?columns=3&header=kitty["Packaging status", link="https://repology.org/project/kitty/versions"]

View File

@@ -1,90 +0,0 @@
#!./kitty/launcher/kitty +launch
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import fcntl
import io
import os
import select
import signal
import struct
import sys
import termios
import time
from pty import CHILD, fork
from kitty.constants import kitten_exe
from kitty.fast_data_types import Screen, safe_pipe
from kitty.utils import read_screen_size
def run_parsing_benchmark(cell_width: int = 10, cell_height: int = 20, scrollback: int = 20000) -> None:
isatty = sys.stdout.isatty()
if isatty:
sz = read_screen_size()
columns, rows = sz.cols, sz.rows
else:
columns, rows = 80, 25
child_pid, master_fd = fork()
is_child = child_pid == CHILD
argv = [kitten_exe(), '__benchmark__', '--with-scrollback']
if is_child:
while read_screen_size().width != columns * cell_width:
time.sleep(0.01)
signal.pthread_sigmask(signal.SIG_SETMASK, ())
os.execvp(argv[0], argv)
# os.set_blocking(master_fd, False)
x_pixels = columns * cell_width
y_pixels = rows * cell_height
s = struct.pack('HHHH', rows, columns, x_pixels, y_pixels)
fcntl.ioctl(master_fd, termios.TIOCSWINSZ, s)
write_buf = b''
r_pipe, w_pipe = safe_pipe(True)
class ToChild:
def write(self, x: bytes | str) -> None:
nonlocal write_buf
if isinstance(x, str):
x = x.encode()
write_buf += x
os.write(w_pipe, b'1')
screen = Screen(None, rows, columns, scrollback, cell_width, cell_height, 0, ToChild())
def parse_bytes(data: bytes) -> None:
data = memoryview(data)
while data:
dest = screen.test_create_write_buffer()
s = screen.test_commit_write_buffer(data, dest)
data = data[s:]
screen.test_parse_written_data()
while True:
rd, wd, _ = select.select([master_fd, r_pipe], [master_fd] if write_buf else [], [])
if r_pipe in rd:
os.read(r_pipe, 256)
if master_fd in rd:
try:
data = os.read(master_fd, io.DEFAULT_BUFFER_SIZE)
except OSError:
data = b''
if not data:
break
parse_bytes(data)
if master_fd in wd:
n = os.write(master_fd, write_buf)
write_buf = write_buf[n:]
if isatty:
lines: list[str] = []
screen.linebuf.as_ansi(lines.append)
sys.stdout.write(''.join(lines))
else:
sys.stdout.write(str(screen.linebuf))
def main() -> None:
run_parsing_benchmark()
if __name__ == '__main__':
main()

View File

@@ -21,12 +21,10 @@ import (
const ( const (
folder = "dependencies" folder = "dependencies"
fonts_folder = "fonts"
macos_prefix = "/Users/Shared/kitty-build/sw/sw" macos_prefix = "/Users/Shared/kitty-build/sw/sw"
macos_python = "python/Python.framework/Versions/Current/bin/python3" macos_python = "python/Python.framework/Versions/Current/bin/python3"
macos_python_framework = "python/Python.framework/Versions/Current/Python" macos_python_framework = "python/Python.framework/Versions/Current/Python"
macos_python_framework_exe = "python/Python.framework/Versions/Current/Resources/Python.app/Contents/MacOS/Python" macos_python_framework_exe = "python/Python.framework/Versions/Current/Resources/Python.app/Contents/MacOS/Python"
NERD_URL = "https://github.com/ryanoasis/nerd-fonts/releases/latest/download/NerdFontsSymbolsOnly.tar.xz"
) )
func root_dir() string { func root_dir() string {
@@ -37,14 +35,6 @@ func root_dir() string {
return f return f
} }
func fonts_dir() string {
f, e := filepath.Abs(fonts_folder)
if e != nil {
exit(e)
}
return f
}
var _ = fmt.Print var _ = fmt.Print
func exit(x any) { func exit(x any) {
@@ -335,19 +325,6 @@ func dependencies(args []string) {
}); err != nil { }); err != nil {
exit(err) exit(err)
} }
tarfile, _ = filepath.Abs(cached_download(NERD_URL))
root = fonts_dir()
if err := os.MkdirAll(root, 0o755); err != nil {
exit(err)
}
cmd = exec.Command("tar", "xf", tarfile, "SymbolsNerdFontMono-Regular.ttf")
cmd.Dir = root
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err = cmd.Run(); err != nil {
exit(err)
}
fmt.Println(`Dependencies downloaded. Now build kitty with: ./dev.sh build`) fmt.Println(`Dependencies downloaded. Now build kitty with: ./dev.sh build`)
} }

View File

@@ -89,7 +89,7 @@ def build_frozen_tools(kitty_exe):
def sanitize_source_folder(path: str) -> None: def sanitize_source_folder(path: str) -> None:
for q in walk(path): for q in walk(path):
if os.path.splitext(q)[1] not in ('.py', '.glsl', '.ttf', '.otf', '.json'): if os.path.splitext(q)[1] not in ('.py', '.glsl', '.ttf', '.otf'):
os.unlink(q) os.unlink(q)

View File

@@ -1,3 +1,3 @@
image 'https://cloud-images.ubuntu.com/releases/bionic/release/ubuntu-18.04-server-cloudimg-{}.img' image 'https://cloud-images.ubuntu.com/releases/bionic/release/ubuntu-18.04-server-cloudimg-{}.img'
deps 'bison flex libxcursor-dev libxrandr-dev libxi-dev libxinerama-dev libgl1-mesa-dev libx11-xcb-dev libxcb-xkb-dev libfontconfig1-dev libdbus-1-dev libsystemd-dev' deps 'bison flex libxcursor-dev libxrandr-dev libxi-dev libxinerama-dev libgl1-mesa-dev libx11-xcb-dev libxcb-xkb-dev libfontconfig1-dev libdbus-1-dev'

View File

@@ -10,12 +10,16 @@ import subprocess
import tarfile import tarfile
import time import time
from bypy.constants import OUTPUT_DIR, PREFIX, python_major_minor_version from bypy.constants import OUTPUT_DIR, PREFIX, is64bit, python_major_minor_version
from bypy.freeze import extract_extension_modules, freeze_python, path_to_freeze_dir from bypy.freeze import extract_extension_modules, freeze_python, path_to_freeze_dir
from bypy.utils import get_dll_path, mkdtemp, py_compile, walk from bypy.utils import get_dll_path, mkdtemp, py_compile, walk
j = os.path.join j = os.path.join
machine = (os.uname()[4] or '').lower() machine = (os.uname()[4] or '').lower()
if machine.startswith('arm64') or machine.startswith('aarch64'):
arch = 'arm64'
else:
arch = 'x86_64' if is64bit else 'i686'
self_dir = os.path.dirname(os.path.abspath(__file__)) self_dir = os.path.dirname(os.path.abspath(__file__))
py_ver = '.'.join(map(str, python_major_minor_version())) py_ver = '.'.join(map(str, python_major_minor_version()))
iv = globals()['init_env'] iv = globals()['init_env']
@@ -24,16 +28,16 @@ kitty_constants = iv['kitty_constants']
def binary_includes(): def binary_includes():
return tuple(map(get_dll_path, ( return tuple(map(get_dll_path, (
'expat', 'sqlite3', 'ffi', 'z', 'lzma', 'png16', 'lcms2', 'ssl', 'crypto', 'crypt', 'expat', 'sqlite3', 'ffi', 'z', 'lzma', 'png16', 'lcms2', 'crypt',
'iconv', 'pcre2-8', 'graphite2', 'glib-2.0', 'freetype', 'xxhash', 'iconv', 'pcre', 'graphite2', 'glib-2.0', 'freetype', 'xxhash',
'pixman-1', 'cairo', 'harfbuzz', 'xkbcommon', 'xkbcommon-x11', 'harfbuzz', 'xkbcommon', 'xkbcommon-x11',
# fontconfig is not bundled because in typical brain dead Linux # fontconfig is not bundled because in typical brain dead Linux
# distro fashion, different distros use different default config # distro fashion, different distros use different default config
# paths for fontconfig. # paths for fontconfig.
'ncursesw', 'readline', 'brotlicommon', 'brotlienc', 'brotlidec', 'ncursesw', 'readline', 'brotlicommon', 'brotlienc', 'brotlidec',
'wayland-client', 'wayland-cursor', 'wayland-client', 'wayland-cursor',
))) + ( ))) + (
get_dll_path('bz2', 2), get_dll_path('bz2', 2), get_dll_path('ssl', 2), get_dll_path('crypto', 2),
get_dll_path(f'python{py_ver}', 2), get_dll_path(f'python{py_ver}', 2),
) )
@@ -194,13 +198,12 @@ def strip_binaries(files):
def create_tarfile(env, compression_level='9'): def create_tarfile(env, compression_level='9'):
print('Creating archive...') print('Creating archive...')
base = OUTPUT_DIR base = OUTPUT_DIR
arch = 'arm64' if 'arm64' in os.environ['BYPY_ARCH'] else ('i686' if 'i386' in os.environ['BYPY_ARCH'] else 'x86_64')
try: try:
shutil.rmtree(base) shutil.rmtree(base)
except OSError as err: except OSError as err:
if err.errno not in (errno.ENOENT, errno.EBUSY): # EBUSY when the directory is mountpoint if err.errno != errno.ENOENT:
raise raise
os.makedirs(base, exist_ok=True) os.mkdir(base)
dist = os.path.join(base, f'{kitty_constants["appname"]}-{kitty_constants["version"]}-{arch}.tar') dist = os.path.join(base, f'{kitty_constants["appname"]}-{kitty_constants["version"]}-{arch}.tar')
with tarfile.open(dist, mode='w', format=tarfile.PAX_FORMAT) as tf: with tarfile.open(dist, mode='w', format=tarfile.PAX_FORMAT) as tf:
cwd = os.getcwd() cwd = os.getcwd()
@@ -213,8 +216,7 @@ def create_tarfile(env, compression_level='9'):
print('Compressing archive...') print('Compressing archive...')
ans = f'{dist.rpartition(".")[0]}.txz' ans = f'{dist.rpartition(".")[0]}.txz'
start_time = time.time() start_time = time.time()
threads = 4 if arch == 'i686' else 0 subprocess.check_call(['xz', '--verbose', '--threads=0', '-f', f'-{compression_level}', dist])
subprocess.check_call(['xz', '--verbose', f'--threads={threads}', '-f', f'-{compression_level}', dist])
secs = time.time() - start_time secs = time.time() - start_time
print('Compressed in {} minutes {} seconds'.format(secs // 60, secs % 60)) print('Compressed in {} minutes {} seconds'.format(secs // 60, secs % 60))
os.rename(f'{dist}.xz', ans) os.rename(f'{dist}.xz', ans)

View File

@@ -1,8 +1,8 @@
# Requires installation of XCode >= 10.3 and go 1.23 and Python 3 and # Requires installation of XCode 10.3 and go 1.19 and Python 3 and
# python3 -m pip install certifi # python3 -m pip install certifi
vm_name 'macos-kitty' vm_name 'macos-kitty'
root '/Users/Shared/kitty-build' root '/Users/Shared/kitty-build'
python '/usr/local/bin/python3' python '/usr/local/bin/python3'
universal 'true' universal 'true'
deploy_target '11.0' deploy_target '10.14'

View File

@@ -130,7 +130,7 @@ def sign_app(app_dir, notarize):
with make_certificate_useable(): with make_certificate_useable():
do_sign(app_dir) do_sign(app_dir)
if notarize: if notarize:
notarize_app(app_dir, 'kitty') notarize_app(app_dir)
class Freeze(object): class Freeze(object):
@@ -292,8 +292,8 @@ class Freeze(object):
'harfbuzz.0', 'harfbuzz.0',
'png16.16', 'png16.16',
'lcms2.2', 'lcms2.2',
'crypto.3', 'crypto.1.1',
'ssl.3', 'ssl.1.1',
'xxhash.0', 'xxhash.0',
): ):
print('\nAdding', x) print('\nAdding', x)
@@ -441,7 +441,7 @@ class Freeze(object):
py_compile(join(self.resources_dir, 'Python')) py_compile(join(self.resources_dir, 'Python'))
@flush @flush
def makedmg(self, d, volname, format='ULMO'): def makedmg(self, d, volname, format='ULFO'):
''' Copy a directory d into a dmg named volname ''' ''' Copy a directory d into a dmg named volname '''
print('\nMaking dmg...') print('\nMaking dmg...')
sys.stdout.flush() sys.stdout.flush()

View File

@@ -2,22 +2,23 @@
# vim:fileencoding=utf-8 # vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net> # License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
import _sitebuiltins
import builtins import builtins
import sys import sys
import _sitebuiltins
def set_quit() -> None:
def set_quit():
eof = 'Ctrl-D (i.e. EOF)' eof = 'Ctrl-D (i.e. EOF)'
builtins.quit = _sitebuiltins.Quitter('quit', eof) builtins.quit = _sitebuiltins.Quitter('quit', eof)
builtins.exit = _sitebuiltins.Quitter('exit', eof) builtins.exit = _sitebuiltins.Quitter('exit', eof)
def set_helper() -> None: def set_helper():
builtins.help = _sitebuiltins._Helper() builtins.help = _sitebuiltins._Helper()
def main() -> None: def main():
sys.argv[0] = sys.calibre_basename sys.argv[0] = sys.calibre_basename
set_helper() set_helper()
set_quit() set_quit()

View File

@@ -2,8 +2,8 @@
{ {
"name": "zlib", "name": "zlib",
"unix": { "unix": {
"filename": "zlib-1.3.1.tar.xz", "filename": "zlib-1.2.13.tar.xz",
"hash": "sha256:38ef96b8dfe510d42707d9c781877914792541133e1870841463bfa73f883e32", "hash": "sha256:d14c38e313afc35a9a8760dadf26042f51ea0f5d154b0630a31da0540107fb98",
"urls": ["https://zlib.net/{filename}"] "urls": ["https://zlib.net/{filename}"]
} }
}, },
@@ -31,8 +31,8 @@
{ {
"name": "openssl", "name": "openssl",
"unix": { "unix": {
"filename": "openssl-3.3.0.tar.gz", "filename": "openssl-1.1.1i.tar.gz",
"hash": "sha256:53e66b043322a606abf0087e7699a0e033a37fa13feb9742df35c3a33b18fb02", "hash": "sha256:e8be6a35fe41d10603c3cc635e93289ed00bf34b79671a3a4de64fcee00d5242",
"urls": ["https://www.openssl.org/source/{filename}"] "urls": ["https://www.openssl.org/source/{filename}"]
} }
}, },
@@ -41,27 +41,28 @@
"name": "cmake", "name": "cmake",
"os": "macos", "os": "macos",
"unix": { "unix": {
"filename": "cmake-3.29.3.tar.gz", "filename": "cmake-3.19.4.tar.gz",
"hash": "sha256:252aee1448d49caa04954fd5e27d189dd51570557313e7b281636716a238bccb", "hash": "sha256:7d0232b9f1c57e8de81f38071ef8203e6820fe7eec8ae46a1df125d88dbcc2e1",
"urls": ["https://cmake.org/files/v3.19/{filename}"] "urls": ["https://cmake.org/files/v3.19/{filename}"]
} }
}, },
{ {
"name": "expat", "name": "expat",
"unix": { "unix": {
"filename": "expat-2.6.2.tar.xz", "filename": "expat-2.2.10.tar.bz2",
"hash": "sha256:ee14b4c5d8908b1bec37ad937607eab183d4d9806a08adee472c3c3121d27364", "hash": "sha256:b2c160f1b60e92da69de8e12333096aeb0c3bf692d41c60794de278af72135a5",
"urls": ["https://github.com/libexpat/libexpat/releases/download/R_2_6_2/{filename}"] "urls": ["https://github.com/libexpat/libexpat/releases/download/R_2_2_10/{filename}"]
} }
}, },
{ {
"name": "libxml2", "name": "libxml2",
"unix": { "unix": {
"filename": "libxml2-2.12.7.tar.xz", "filename": "libxml2-2.9.12.tar.gz",
"hash": "sha256:24ae78ff1363a973e6d8beba941a7945da2ac056e19b53956aeb6927fd6cfb56", "hash": "sha256:c8d6681e38c56f172892c85ddc0852e1fd4b53b4209e7f4ebf17f7e2eae71d92",
"urls": ["https://download.gnome.org/sources/libxml2/2.12/{filename}"] "urls": ["ftp://xmlsoft.org/libxml2/{filename}"]
} }
}, },
@@ -69,8 +70,8 @@
"name": "xkbcommon", "name": "xkbcommon",
"os": "linux", "os": "linux",
"unix": { "unix": {
"filename": "libxkbcommon-1.7.0.tar.xz", "filename": "libxkbcommon-1.0.3.tar.xz",
"hash": "sha256:65782f0a10a4b455af9c6baab7040e2f537520caa2ec2092805cdfd36863b247", "hash": "sha256:a2202f851e072b84e64a395212cbd976ee18a8ee602008b0bad02a13247dbc52",
"urls": ["https://xkbcommon.org/download/{filename}"] "urls": ["https://xkbcommon.org/download/{filename}"]
} }
}, },
@@ -78,19 +79,20 @@
{ {
"name": "sqlite", "name": "sqlite",
"unix": { "unix": {
"filename": "sqlite-autoconf-3450300.tar.gz", "filename": "sqlite-autoconf-3340100.tar.gz",
"hash": "sha256:b2809ca53124c19c60f42bf627736eae011afdcc205bb48270a5ee9a38191531", "hash": "sha256:2a3bca581117b3b88e5361d0ef3803ba6d8da604b1c1a47d902ef785c1b53e89",
"urls": ["https://www.sqlite.org/2024/{filename}"] "urls": ["https://www.sqlite.org/2021/{filename}"]
} }
}, },
{ {
"name": "libffi", "name": "libffi",
"os": "linux", "os": "linux",
"unix": { "unix": {
"filename": "libffi-3.4.6.tar.gz", "filename": "libffi-3.3.0.tar.gz",
"hash": "sha256:b0dea9df23c863a7a50e825440f3ebffabd65df1497108e5d437747843895a4e", "hash": "sha256:72fba7922703ddfa7a028d513ac15a85c8d54c8d67f55fa5a4802885dc652056",
"urls": ["https://github.com/libffi/libffi/releases/download/v3.4.6/{filename}"] "urls": ["https://github.com/libffi/libffi/releases/download/v3.3/libffi-3.3.tar.gz"]
} }
}, },
@@ -98,9 +100,9 @@
"name": "ncurses", "name": "ncurses",
"os": "linux", "os": "linux",
"unix": { "unix": {
"filename": "ncurses-6.5.tar.gz", "filename": "ncurses-6.2.tar.gz",
"hash": "sha256:136d91bc269a9a5785e5f9e980bc76ab57428f604ce3e5a5a90cebc767971cc6", "hash": "sha256:30306e0c76e0f9f1f0de987cf1c82a5c21e1ce6568b9227f7da5b71cbea86c9d",
"urls": ["https://ftp.gnu.org/gnu/ncurses/{filename}"] "urls": ["https://invisible-mirror.net/archives/ncurses/{filename}"]
} }
}, },
@@ -108,8 +110,8 @@
"name": "readline", "name": "readline",
"os": "linux", "os": "linux",
"unix": { "unix": {
"filename": "readline-8.2.tar.gz", "filename": "readline-8.1.tar.gz",
"hash": "sha256:3feb7171f16a84ee82ca18a36d7b9be109a52c04f492a053331d7d1095007c35", "hash": "sha256:f8ceb4ee131e3232226a17f51b164afc46cd0b9e6cef344be87c65962cb82b02",
"urls": ["https://ftp.gnu.org/gnu/readline/{filename}"] "urls": ["https://ftp.gnu.org/gnu/readline/{filename}"]
} }
}, },
@@ -136,26 +138,26 @@
"name": "xcrypt", "name": "xcrypt",
"os": "linux", "os": "linux",
"unix": { "unix": {
"filename": "xcrypt-4.4.36.tar.gz", "filename": "xcrypt-4.4.17.tar.gz",
"hash": "sha256:b979838d5f1f238869d467484793b72b8bca64c4eae696fdbba0a9e0b6c28453", "hash": "sha256:7665168d0409574a03f7b484682e68334764c29c21ca5df438955a381384ca07",
"urls": ["https://github.com/besser82/libxcrypt/archive/v4.4.36.tar.gz"] "urls": ["https://github.com/besser82/libxcrypt/archive/v4.4.17.tar.gz"]
} }
}, },
{ {
"name": "python", "name": "python",
"unix": { "unix": {
"filename": "Python-3.12.3.tar.xz", "filename": "Python-3.11.6.tar.xz",
"hash": "sha256:56bfef1fdfc1221ce6720e43a661e3eb41785dd914ce99698d8c7896af4bdaa1", "hash": "sha256:0fab78fa7f133f4f38210c6260d90d7c0d5c7198446419ce057ec7ac2e6f5f38",
"urls": ["https://www.python.org/ftp/python/3.12.3/{filename}"] "urls": ["https://www.python.org/ftp/python/3.11.6/{filename}"]
} }
}, },
{ {
"name": "libpng", "name": "libpng",
"unix": { "unix": {
"filename": "libpng-1.6.43.tar.xz", "filename": "libpng-1.6.37.tar.xz",
"hash": "sha256:6a5ca0652392a2d7c9db2ae5b40210843c0bbc081cbd410825ab00cc59f14a6c", "hash": "sha256:505e70834d35383537b6491e7ae8641f1a4bed1876dbfe361201fc80868d88ca",
"urls": ["https://downloads.sourceforge.net/sourceforge/libpng/{filename}"] "urls": ["https://downloads.sourceforge.net/sourceforge/libpng/{filename}"]
} }
}, },
@@ -163,9 +165,9 @@
{ {
"name": "lcms2", "name": "lcms2",
"unix": { "unix": {
"filename": "lcms2-2.16.tar.gz", "filename": "lcms2-2.12.tar.gz",
"hash": "sha256:d873d34ad8b9b4cea010631f1a6228d2087475e4dc5e763eb81acc23d9d45a51", "hash": "sha256:e501f1482fc424550ef3abbf86bf1c66090e1661249e89552d39ed5bf935df66",
"urls": ["https://github.com/mm2/Little-CMS/archive/2.16/{filename}"] "urls": ["https://github.com/mm2/Little-CMS/archive/2.12/{filename}"]
} }
}, },
@@ -183,9 +185,9 @@
"name": "pcre", "name": "pcre",
"os": "linux", "os": "linux",
"unix": { "unix": {
"filename": "pcre2-10.43.tar.bz2", "filename": "pcre-8.44.tar.bz2",
"hash": "sha256:e2a53984ff0b07dfdb5ae4486bbb9b21cca8e7df2434096cc9bf1b728c350bcb", "hash": "sha256:19108658b23b3ec5058edc9f66ac545ea19f9537234be1ec62b714c84399366d",
"urls": ["https://github.com/PCRE2Project/pcre2/releases/download/pcre2-10.43/{filename}"] "urls": ["https://ftp.pcre.org/pub/pcre/{filename}"]
} }
}, },
@@ -193,39 +195,19 @@
"name": "iconv", "name": "iconv",
"os": "linux", "os": "linux",
"unix": { "unix": {
"filename": "libiconv-1.17.tar.gz", "filename": "libiconv-1.16.tar.gz",
"hash": "sha256:8f74213b56238c85a50a5329f77e06198771e70dd9a739779f4c02f65d971313", "hash": "sha256:e6a1b1b589654277ee790cce3734f07876ac4ccfaecbee8afa0b649cf529cc04",
"urls": ["https://ftp.gnu.org/pub/gnu/libiconv/{filename}"] "urls": ["https://ftp.gnu.org/pub/gnu/libiconv/{filename}"]
} }
}, },
{
"name": "installer",
"comment": "Needed infrastructure for installing pure python packages (wheels)",
"unix": {
"filename": "installer-0.7.0-py3-none-any.whl",
"hash": "sha256:05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53",
"urls": ["pypi"]
}
},
{
"name": "packaging",
"comment": "Needed for glib for some absurd reason",
"unix": {
"filename": "packaging-23.1-py3-none-any.whl",
"hash": "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61",
"urls": ["pypi"]
}
},
{ {
"name": "glib", "name": "glib",
"os": "linux", "os": "linux",
"unix": { "unix": {
"filename": "glib-2.80.2.tar.xz", "filename": "glib-2.66.6.tar.xz",
"hash": "sha256:b9cfb6f7a5bd5b31238fd5d56df226b2dda5ea37611475bf89f6a0f9400fe8bd", "hash": "sha256:80fff9c63d2725834328071c42003c311f77f91caf2285195c587c62f5638329",
"urls": ["https://download.gnome.org/sources/glib/2.80/{filename}"] "urls": ["https://ftp.gnome.org/pub/GNOME/sources/glib/2.66/{filename}"]
} }
}, },
@@ -233,19 +215,9 @@
"name": "brotli", "name": "brotli",
"os": "linux", "os": "linux",
"unix": { "unix": {
"filename": "brotli-1.1.0.tar.gz", "filename": "brotli-1.0.9.tar.gz",
"hash": "sha256:e720a6ca29428b803f4ad165371771f5398faba397edf6778837a18599ea13ff", "hash": "sha256:f9e8d81d0405ba66d181529af42a3354f838c939095ff99930da6aa9cdf6fe46",
"urls": ["https://github.com/google/brotli/archive/v1.1.0/{filename}"] "urls": ["https://github.com/google/brotli/archive/v1.0.9/{filename}"]
}
},
{
"name": "pixman",
"os": "linux",
"unix": {
"filename": "pixman-0.44.2.tar.xz",
"hash": "sha256:50baf820dde0c5ff9714d03d2df4970f606a3d3b1024f5404c0398a9821cc4b0",
"urls": ["https://www.cairographics.org/releases/pixman-0.44.2.tar.xz"]
} }
}, },
@@ -253,8 +225,8 @@
"name": "freetype", "name": "freetype",
"os": "linux", "os": "linux",
"unix": { "unix": {
"filename": "freetype-2.13.2.tar.xz", "filename": "freetype-2.10.4.tar.xz",
"hash": "sha256:12991c4e55c506dd7f9b765933e62fd2be2e06d421505d7950a132e4f1bb484d", "hash": "sha256:86a854d8905b19698bbc8f23b860bc104246ce4854dcea8e3b0fb21284f75784",
"urls": ["https://download.savannah.gnu.org/releases/freetype/{filename}"] "urls": ["https://download.savannah.gnu.org/releases/freetype/{filename}"]
} }
}, },
@@ -269,28 +241,17 @@
} }
}, },
{
"name": "cairo",
"os": "linux",
"unix": {
"filename": "cairo-1.18.2.tar.xz",
"hash": "sha256:a62b9bb42425e844cc3d6ddde043ff39dbabedd1542eba57a2eb79f85889d45a",
"urls": ["https://www.cairographics.org/releases/cairo-1.18.2.tar.xz"]
}
},
{ {
"name": "harfbuzz", "name": "harfbuzz",
"unix": { "unix": {
"filename": "harfbuzz-8.5.0.tar.xz", "filename": "harfbuzz-8.2.2.tar.xz",
"hash": "sha256:77e4f7f98f3d86bf8788b53e6832fb96279956e1c3961988ea3d4b7ca41ddc27", "hash": "sha256:e433ad85fbdf57f680be29479b3f964577379aaf319f557eb76569f0ecbc90f3",
"urls": ["https://github.com/harfbuzz/harfbuzz/releases/download/8.5.0/{filename}"] "urls": ["https://github.com/harfbuzz/harfbuzz/releases/download/8.2.2/{filename}"]
} }
}, },
{ {
"name": "simde", "name": "simde",
"comment": "Cannot update till gcc in the build VM is updated as simde 0.8 requires newer gcc",
"unix": { "unix": {
"filename": "simde-amalgamated-0.7.6.tar.xz", "filename": "simde-amalgamated-0.7.6.tar.xz",
"hash": "sha256:703eac1f2af7de1f7e4aea2286130b98e1addcc0559426e78304c92e2b4eb5e1", "hash": "sha256:703eac1f2af7de1f7e4aea2286130b98e1addcc0559426e78304c92e2b4eb5e1",
@@ -302,9 +263,9 @@
"name": "wayland", "name": "wayland",
"os": "linux", "os": "linux",
"unix": { "unix": {
"filename": "wayland-1.23.0.tar.xz", "filename": "wayland-1.22.0.tar.xz",
"hash": "sha256:05b3e1574d3e67626b5974f862f36b5b427c7ceeb965cb36a4e6c2d342e45ab2", "hash": "sha256:1540af1ea698a471c2d8e9d288332c7e0fd360c8f1d12936ebb7e7cbc2425842",
"urls": ["https://gitlab.freedesktop.org/wayland/wayland/-/releases/1.23.0/downloads/{filename}"] "urls": ["https://gitlab.freedesktop.org/wayland/wayland/-/releases/1.22.0/downloads/{filename}"]
} }
}, },
@@ -312,9 +273,9 @@
"name": "wayland-protocols", "name": "wayland-protocols",
"os": "linux", "os": "linux",
"unix": { "unix": {
"filename": "wayland-protocols-1.41.tar.xz", "filename": "wayland-protocols-1.32.tar.xz",
"hash": "sha256:2786b6b1b79965e313f2c289c12075b9ed700d41844810c51afda10ee329576b", "hash": "sha256:7459799d340c8296b695ef857c07ddef24c5a09b09ab6a74f7b92640d2b1ba11",
"urls": ["https://gitlab.freedesktop.org/wayland/wayland-protocols/-/releases/1.41/downloads/{filename}"] "urls": ["https://gitlab.freedesktop.org/wayland/wayland-protocols/-/releases/1.32/downloads/{filename}"]
} }
} }

View File

@@ -40,7 +40,7 @@ Action Shortcut
======================== ======================= ======================== =======================
New tab :sc:`new_tab` (also :kbd:`⌘+t` on macOS) New tab :sc:`new_tab` (also :kbd:`⌘+t` on macOS)
Close tab :sc:`close_tab` (also :kbd:`⌘+w` on macOS) Close tab :sc:`close_tab` (also :kbd:`⌘+w` on macOS)
Next tab :sc:`next_tab` (also :kbd:`⇧+⌃+⇥` and :kbd:`⇧+⌘+]` on macOS) Next tab :sc:`next_tab` (also :kbd:`⌃+⇥` and :kbd:`⇧+⌘+]` on macOS)
Previous tab :sc:`previous_tab` (also :kbd:`⇧+⌃+⇥` and :kbd:`⇧+⌘+[` on macOS) Previous tab :sc:`previous_tab` (also :kbd:`⇧+⌃+⇥` and :kbd:`⇧+⌘+[` on macOS)
Next layout :sc:`next_layout` Next layout :sc:`next_layout`
Move tab forward :sc:`move_tab_forward` Move tab forward :sc:`move_tab_forward`

View File

@@ -23,9 +23,7 @@ simply re-run the command.
**Do not** copy the kitty binary out of the installation folder. If you want **Do not** copy the kitty binary out of the installation folder. If you want
to add it to your :envvar:`PATH`, create a symlink in :file:`~/.local/bin` or to add it to your :envvar:`PATH`, create a symlink in :file:`~/.local/bin` or
:file:`/usr/bin` or wherever. You should create a symlink for the :file:`kitten` :file:`/usr/bin` or wherever. You should create a symlink for the :file:`kitten`
binary as well. Whichever folder you choose to create the symlink in should binary as well.
be in the **systemwide** PATH, a folder added to the PATH in your shell rc
files will not work when running kitty from your desktop environment.
Manually installing Manually installing
@@ -56,16 +54,14 @@ particular desktop, but it should work for most major desktop environments.
cp ~/.local/kitty.app/share/applications/kitty.desktop ~/.local/share/applications/ cp ~/.local/kitty.app/share/applications/kitty.desktop ~/.local/share/applications/
# If you want to open text files and images in kitty via your file manager also add the kitty-open.desktop file # If you want to open text files and images in kitty via your file manager also add the kitty-open.desktop file
cp ~/.local/kitty.app/share/applications/kitty-open.desktop ~/.local/share/applications/ cp ~/.local/kitty.app/share/applications/kitty-open.desktop ~/.local/share/applications/
# Update the paths to the kitty and its icon in the kitty desktop file(s) # Update the paths to the kitty and its icon in the kitty.desktop file(s)
sed -i "s|Icon=kitty|Icon=$(readlink -f ~)/.local/kitty.app/share/icons/hicolor/256x256/apps/kitty.png|g" ~/.local/share/applications/kitty*.desktop sed -i "s|Icon=kitty|Icon=/home/$USER/.local/kitty.app/share/icons/hicolor/256x256/apps/kitty.png|g" ~/.local/share/applications/kitty*.desktop
sed -i "s|Exec=kitty|Exec=$(readlink -f ~)/.local/kitty.app/bin/kitty|g" ~/.local/share/applications/kitty*.desktop sed -i "s|Exec=kitty|Exec=/home/$USER/.local/kitty.app/bin/kitty|g" ~/.local/share/applications/kitty*.desktop
# Make xdg-terminal-exec (and hence desktop environments that support it use kitty)
echo 'kitty.desktop' > ~/.config/xdg-terminals.list
.. note:: .. note::
In :file:`kitty-open.desktop`, kitty is registered to handle some supported In :file:`kitty-open.desktop`, kitty is registered to handle some supported
MIME types. This will cause kitty to take precedence on some systems where MIME types. This will cause kitty to take precedence on some systems where
the default apps are not explicitly set. For example, if you expect to use the default apps are not explicitly set. For example, you expect to use
other GUI file managers to open dir paths when using commands such as other GUI file managers to open dir paths when using commands such as
:program:`xdg-open`, you should configure the default opener for the MIME :program:`xdg-open`, you should configure the default opener for the MIME
type ``inode/directory``:: type ``inode/directory``::
@@ -101,12 +97,12 @@ Customizing the installation
_kitty_install_cmd \ _kitty_install_cmd \
installer=nightly dest=/some/other/location installer=nightly dest=/some/other/location
* You can specify a specific version to install, with: * You can specify a different install location, with ``dest``:
.. code-block:: sh .. code-block:: sh
_kitty_install_cmd \ _kitty_install_cmd \
installer=version-0.35.2 dest=/some/other/location
* You can tell the installer not to launch |kitty| after installing it with * You can tell the installer not to launch |kitty| after installing it with
``launch=n``: ``launch=n``:

View File

@@ -13,8 +13,7 @@ Build from source
|kitty| is designed to run from source, for easy hack-ability. All you need to |kitty| is designed to run from source, for easy hack-ability. All you need to
get started is a C compiler and the `go compiler get started is a C compiler and the `go compiler
<https://go.dev/doc/install>`__ (on Linux, the :ref:`X11 development libraries <x11-dev-libs>` as well). <https://go.dev/doc/install>`__. After installing those, run the following commands::
After installing those, run the following commands::
git clone https://github.com/kovidgoyal/kitty.git && cd kitty git clone https://github.com/kovidgoyal/kitty.git && cd kitty
./dev.sh build ./dev.sh build
@@ -24,8 +23,7 @@ That's it, kitty will be built from source, magically. You can run it as
This works, because the :code:`./dev.sh build` command downloads all the major This works, because the :code:`./dev.sh build` command downloads all the major
dependencies of kitty as pre-built binaries for your platform and builds kitty dependencies of kitty as pre-built binaries for your platform and builds kitty
to use these rather than system libraries. The few required system libraries to use these rather than system libraries.
are X11 and DBUS on Linux.
If you make changes to kitty code, simply re-run :code:`./dev.sh build` If you make changes to kitty code, simply re-run :code:`./dev.sh build`
to build kitty with your changes. to build kitty with your changes.
@@ -89,12 +87,9 @@ Run-time dependencies:
* ``liblcms2`` * ``liblcms2``
* ``libxxhash`` * ``libxxhash``
* ``openssl`` * ``openssl``
* ``pixman`` (not needed on macOS)
* ``cairo`` (not needed on macOS)
* ``freetype`` (not needed on macOS) * ``freetype`` (not needed on macOS)
* ``fontconfig`` (not needed on macOS) * ``fontconfig`` (not needed on macOS)
* ``libcanberra`` (not needed on macOS) * ``libcanberra`` (not needed on macOS)
* ``libsystemd`` (optional, not needed on non systemd systems)
* ``ImageMagick`` (optional, needed to display uncommon image formats in the terminal) * ``ImageMagick`` (optional, needed to display uncommon image formats in the terminal)
@@ -104,23 +99,10 @@ Build-time dependencies:
* ``simde`` * ``simde``
* ``go`` >= _build_go_version (see :file:`go.mod` for go packages used during building) * ``go`` >= _build_go_version (see :file:`go.mod` for go packages used during building)
* ``pkg-config`` * ``pkg-config``
* Symbols NERD Font Mono either installed system-wide or placed in :file:`fonts/SymbolsNerdFontMono-Regular.ttf`
* For building on Linux in addition to the above dependencies you might also * For building on Linux in addition to the above dependencies you might also
need to install the following packages, if they are not already installed by need to install the following packages, if they are not already installed by
your distro: your distro:
- ``liblcms2-dev``
- ``libfontconfig-dev``
- ``libssl-dev``
- ``libpython3-dev``
- ``libxxhash-dev``
- ``libsimde-dev``
- ``libcairo2-dev``
.. _x11-dev-libs:
Also, the X11 development libraries:
- ``libdbus-1-dev`` - ``libdbus-1-dev``
- ``libxcursor-dev`` - ``libxcursor-dev``
- ``libxrandr-dev`` - ``libxrandr-dev``
@@ -130,7 +112,11 @@ Build-time dependencies:
- ``libxkbcommon-x11-dev`` - ``libxkbcommon-x11-dev``
- ``libfontconfig-dev`` - ``libfontconfig-dev``
- ``libx11-xcb-dev`` - ``libx11-xcb-dev``
- ``liblcms2-dev``
- ``libssl-dev``
- ``libpython3-dev``
- ``libxxhash-dev``
- ``libsmide-dev``
Build and run from source with Nix Build and run from source with Nix

View File

@@ -9,75 +9,8 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
Recent major new features Recent major new features
--------------------------- ---------------------------
Multiple sized text [0.40] Cheetah speed 🐆
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~
kitty is the first major terminal to introduce the concept of multiple sized
text. Terminal programs running in kitty can now opt-in to use and display text
in multiple font sizes both larger and smaller than the base font size. This is
done in a backwards compatible, opt-in way that does not affect how traditional
terminal programs work at all. For details on the new feature and how to use
it, see :doc:`text-sizing-protocol`.
Cursor trails [0.37]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Show an animated trail when the text cursor makes large jumps making it easy
to follow cursor movements. Inspired by the similar feature in neovide, but
works with terminal multiplexers and kitty windows as well. See :pull:`the pull
request <7970>` for a demonstration video. This feature is optional and must be
turned on by the :opt:`cursor_trail` option in :file:`kitty.conf`.
Variable font support [0.36]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Terminal aficionados spend all day staring at text, so getting text
rendering just right is very important. In that spirit, kitty now supports
`OpenType Variable fonts <https://en.wikipedia.org/wiki/Variable_font>`__.
These allow precise customisation of font characteristics, such as weight and
spacing. Not only that, kitty now has a new :doc:`choose-fonts
<kittens/choose-fonts>` kitten that provides a UI for choosing fonts with
support for font features, variable fonts and previews of how the font will
look. This is in addition to its existing best-in-class font customization
abilities, such as: :opt:`symbol_map`, :opt:`text_composition_strategy`,
:opt:`font_features` and :opt:`modify_font`. kitty knows text rendering is
important, and goes the extra mile for it.
Desktop notifications [0.36]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|kitty| now has a :doc:`notify </kittens/notify>` kitten that can be used to
display desktop notifications from the command line, even over SSH. It has
support for icons, buttons, updating notifications, waiting till
the notification is closed, etc. The underlying :doc:`desktop-notifications`
protocol has been expanded to support all these features.
Wayland goodies [0.34]
~~~~~~~~~~~~~~~~~~~~~~~
Wayland users should rejoice as kitty now comes with major Wayland
quality-of-life improvements:
* Draw GPU accelerated :doc:`desktop panels and background </kittens/panel>`
running arbitrary terminal programs. For example, run `btop
<https://github.com/aristocratos/btop/>`__ as your desktop background
* Background blur for transparent windows is now supported under KDE
using a custom KDE specific protocol
* The kitty window decorations in GNOME are now fully functional with buttons
and they follow system dark/light mode automatically
* kitty now supports fractional scaling in Wayland which means pixel perfect
rendering when you use a fractional scale with no wasted performance on
resizing an overdrawn pixmap in the compositor
With this release kitty's Wayland support is now on par with X11, provided
you use a decent Wayland compositor.
Cheetah speed 🐆 [0.33]
~~~~~~~~~~~~~~~~~~~~~~~~~
kitty has grown up and become a cheetah. It now parses data it receives in kitty has grown up and become a cheetah. It now parses data it receives in
parallel :iss:`using SIMD vector CPU instructions <7005>` for a 2x speedup in parallel :iss:`using SIMD vector CPU instructions <7005>` for a 2x speedup in
@@ -89,494 +22,47 @@ kitty was already so fast that its performance was never a bottleneck, this
improvement makes it even faster and more importantly reduces the energy improvement makes it even faster and more importantly reduces the energy
consumption to do the same tasks. consumption to do the same tasks.
File transfer over the tty device
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Transfer files to and from remote computers over the ``TTY`` device itself.
This means that file transfer works over nested SSH sessions, serial links,
etc. Anywhere you have a terminal device, you can transfer files.
Simply ssh into a remote computer using the :doc:`ssh kitten </kittens/ssh>`
and run the :doc:`transfer kitten </kittens/transfer>` (which the ssh kitten
makes available for you on the remote computer automatically). For example, to
copy a file from a remote computer::
<local computer> $ kitten ssh my-remote-computer
<remote computer> $ kitten transfer some-file /path/on/local/computer
The kitten can transfer files to and from the remote computer. It supports
recursive transfer of directories, symlinks and hardlinks. It can even use the
rsync algorithm to speed up repeated transfers of large files.
.. }}} .. }}}
Detailed list of changes Detailed list of changes
------------------------------------- -------------------------------------
0.42.0 [future] 0.33.2 [future]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Allow configuring the mouse unhide behavior when using :opt:`mouse_hide_wait` (:pull:`8508`)
- diff kitten: Add half page and full page scroll vim-like bindings (:pull:`8514`)
- Fix a regression that caused automatic color themes to not be re-applied after config file reload (:iss:`8530`)
- Wayland: When the compositor supports the `xdg-system-bell
<https://wayland.app/protocols/xdg-system-bell-v1>`__ protocol use it to play
the default bell sound
0.41.1 [2025-04-03]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Fix a regression in the previous release that caused rendering of emoji using
the VS16 variation selector to fail with some fonts (:iss:`8495`)
- Fix a regression in 0.40.0 that caused tab bar margins to not be properly blanked when
the tab bar is at the bottom (:iss:`8494`)
- Wayland: panel kitten: Fix incorrect initial font size on compositors such as Hyprland
that set scale late in the window creation process (:iss:`8496`)
- Fix a regression in 0.40.1 that caused hyperlink underline on hover to remain
on screen when the screen is scrolled
0.41.0 [2025-03-29]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- A new mode of operation for :opt:`text_fg_override_threshold` to override
foreground colors so as to maintain a minimum contrast between foreground and
background text colors. Works in a perceptual color space for best color accuracy
(:pull:`8420`)
- A 15% improvement in throughput when processing text thanks to using a
multi-stage table for Unicode property lookups
- :ref:`kitty +open <launch_actions>`: Ask for confirmation by default when running executables
to work around some badly designed programs that try to open links in
documents that point to executable files. Can be overridden by specifying
your own :file:`launch-actions.conf`.
- Fix a regression in version 0.40.0 causing a crash when the underline
thickness of the font is zero (:iss:`8443`)
- Fix a regression in version 0.40.0 causing a hang on resizing with a wide
character at the right edge of a line that needs to be moved onto the next
line (:iss:`8464`)
- Fix a regression in 0.40.1 that caused copying to clipboard via OSC 52 from
applications that don't specify a destination in the escape code not working
(:iss:`8459`)
- Wayland: Fix a regression in the previous release that caused crashes on
compositors that don't support the xdg-toplevel-icon protocol and the user has
set a custom kitty icon (:iss:`8471`)
0.40.1 [2025-03-18]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Do not count background processes by default for :opt:`confirm_os_window_close` (:iss:`8358`)
- A new option :opt:`clear_selection_on_clipboard_loss` to clear selections when they no longer reflect the contents of the clipboard
- Fix a regression in the previous release that caused empty lines to be skipped when copying text from a selection (:iss:`8435`)
- Fix flickering of hyperlink underline when client program continuously
redraws on mouse movement (:iss:`8414`)
- Wayland: Allow overriding the kitty OS Window icon on compositors that implement the xdg-toplevel-icon protocol
- macOS: When the program running in kitty reports progress information for a task, show a progress bar on the kitty dock icon
- macOS: Fix a regression causing a crash when using :opt:`focus_follows_mouse` (:iss:`8437`)
- OSC 52: Fix specifying both clipboard and primary in OSC 52 requests not supported
0.40.0 [2025-03-08]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- :doc:`Allow terminal programs to use text in different font sizes <text-sizing-protocol>` (:iss:`8226`)
- When rendering underlines add gaps around text descenders (parts of the text
that overlap with the underline). Controlled by the new option :opt:`underline_exclusion` (:iss:`8226`)
- Finally fix the issue of text-width mismatches that has been plaguing the
terminal ecosystem for decades by allowing terminal programs to specify how
many cells to render a piece of text in (:iss:`8226`)
- **Behavior change**: The :opt:`notify_on_cmd_finish` option now uses OS
Window visibility instead of focus state when set to ``invisible`` on
platforms that support querying OS window visibility (:iss:`8320`)
- launch: Add options :option:`launch --source-window` and :option:`launch --next-to` to allow
specifying which window is used as the data source and destination location independently of the
currently active window (:iss:`8295`)
- Linux: Add support for `COLRv1 <https://nabla.typearture.com/whatisCOLRV1.html>`__ fonts. These are typically emoji fonts that use vector images for emoji
- Add support for the octant box-drawing characters
- Speed up rendering of box drawing characters by moving the implementation to native code
- When confirming if a window should be closed consider it active if it has running background processes (:iss:`8358`)
- Remote control: `kitten @ scroll-window`: Allow scrolling to previous/next prompt
- macOS: Fix fallback font rendering for bold/italic text not working for some symbols that are present in the Menlo regular face but not the bold/italic faces (:iss:`8282`)
- XTGETTCAP: Fix response invalid for empty string capabilities (:pull:`8304`)
- ssh kitten: Fix incorrect copying of data files when using the python interpreter and also fix incorrect hard link detection (:disc:`8308`)
- Fix a regression in the previous release that broke setting of nullable colors
- Fix a regression in 0.39.0 that caused a crash on invalid Unicode with a
large number of combining characters in a single cell (:iss:`8318`)
- Fix ``--hold`` always restoring cursor to block shape instead of respecting the value of :opt:`cursor_shape` (:disc:`8344`)
- When dragging in rectangle select mode use a crosshair mouse cursor configurable via :opt:`pointer_shape_when_dragging`
- macOS: notify kitten: Fix waiting for result from desktop notification not working (:disc:`8379`)
- Wayland: Fix mouse pointer position update not being sent when focus regained (:iss`8397`, :iss:`8398`)
- Fix cursor blink animation when :opt:`background_opacity` is less than one (:iss:`8401`)
- Wayland: panel kitten: Add a :code:`center` mode for creating panels to ease
creation of centered popups in Wayland (:pull:`8411`)
0.39.1 [2025-02-01]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Splits layout: Allow setting the bias of the current split using ``layout_action bias`` (:iss:`8222`)
- hints kitten: Workaround for some broken light color themes that make the hints text color too low contrast to read (:iss:`7330`)
- Wayland niri: Fix 250ms delay on startup when using scale 1 (:iss:`8236`)
- :ref:`Watchers <watchers>`: Add a new event ``on_color_scheme_preference_change`` (:iss:`8246`)
0.39.0 [2025-01-16]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- :doc:`diff kitten <kittens/diff>`: Automatically use dark/light color scheme based on the color scheme of the parent terminal. Can be controlled via the new :opt:`kitten-diff.color_scheme` option. Note that this is a **default behavior change** (:iss:`8170`)
- Allow dynamically generating configuration by running an arbitrary program using the new :code:`geninclude` directive in :file:`kitty.conf`
- When a program running in kitty reports progress of a task display it as a percentage in the tab title. Controlled by the :opt:`tab_title_template` option
- When mapping a custom kitten allow using shell escaping for the kitten path (:iss:`8178`)
- Fix border colors not being changed by auto light/dark themes at startup (:iss:`8180`)
- ssh kitten: Fix kitten not being on PATH when SSHing into Debian systems (:iss:`7160`)
- diff kitten: Abort when run inside a terminal that does not support the kitty keyboard protocol (:iss:`8185`)
- :doc:`query kitten <kittens/query_terminal>`: Add support for reporting name of the OS the terminal emulator is running on (:iss:`8201`)
- macOS: Allow using the Passwords app to autofill passwords via the Edit->Autofill menu mimicking other macOS applications (:pull:`8195`)
- macOS: Add menu items to the Edit menu to clear the screen and scrollback
- Fix the :ac:`clear_terminal scrollback <clear_terminal>` action also clearing screen, not just the scrollback
- When reloading configuration fix auto color themes not being re-applied (:iss:`8203`)
0.38.1 [2024-12-26]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- macOS: Fix a regression in the previous release that broke rendering of Emoji using the VS16 variation selector (:iss:`8130`)
- When automatically changing colors based on OS color preference, first reset
all colors to default before applying the new theme so that even colors not
specified in the theme are correct (:iss:`8124`)
- Graphics: Fix deleted but not freed images without any placements being incorrectly freed on a subsequent delete command (:disc:`8129`)
- Graphics: Fix deletion of images by id not working for images with no placements (:disc:`8129`)
- Add support for `escape code protocol <https://github.com/contour-terminal/contour/blob/master/docs/vt-extensions/color-palette-update-notifications.md>`__ for notifying applications on dark/light color scheme change
- Cursor trails: Fix pure vertical movement sometimes not triggering a trail and holding down a key in nvim causing the trail to be glitchy (:pull:`8152`, :pull:`8153`)
- macOS: Fix mouse cursor shape not always being reset to text cursor when mouse re-enters kitty (:iss:`8155`)
- clone-in-kitty: Fix :envvar:`KITTY_WINDOW_ID` being cloned and thus having incorrect value (:iss:`8161`)
0.38.0 [2024-12-15]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Allow :ref:`specifying individual color themes <auto_color_scheme>` to use so that kitty changes colors automatically following the OS dark/light mode
- :opt:`notify_on_cmd_finish`: Automatically remove notifications when the window gains focus or the next notification is shown. Clearing behavior can be configured (:pull:`8100`)
- Discard OSC 9 notifications that start with :code:`4;` because some misguided software is using it for "progress reporting" (:iss:`8011`)
- Wayland GNOME: Workaround bug in mutter causing double tap on titlebar to not always work (:iss:`8054`)
- clipboard kitten: Fix a bug causing kitten to hang in filter mode when input data size is not divisible by 3 and larger than 8KB (:iss:`8059`)
- Wayland: Fix an abort when a client program tries to set an invalid title containing interleaved escape codes and UTF-8 multi-byte characters (:iss:`8067`)
- Graphics protocol: Fix delete by number not deleting newest image with the specified number (:iss:`8071`)
- Fix dashed and dotted underlines not being drawn at the same y position as straight underlines at all font sizes (:iss:`8074`)
- panel kitten: Allow creating floating and on-top panels with arbitrary placement and size on Wayland (:pull:`8068`)
- :opt:`remote_control_password`: Fix using a password without any actions not working (:iss:`8082`)
- Fix enlarging window when a long line is wrapped between the first line of the scrollback buffer and the screen inserting a spurious newline (:iss:`7033`)
- When re-attaching a detached tab preserve internal layout state such as biases and orientations (:iss:`8106`)
- hints/unicode_input kittens: Do not lose keypresses that are sent very rapidly via an automation tool immediately after the kitten is launched (:iss:`7089`)
0.37.0 [2024-10-30]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- A new optional :opt:`text cursor movement animation <cursor_trail>` that
shows a "trail" following the movement of the cursor making it easy to follow
large cursor jumps (:pull:`7970`)
- Custom kittens: Add :ref:`a framework <kitten_main_rc>` for easily and securely using remote control from within a kitten's :code:`main()` function
- kitten icat: Fix the :option:`kitty +kitten icat --no-trailing-newline` not working when using unicode placeholders (:iss:`7948`)
- :opt:`tab_title_template` allow using the 256 terminal colors for formatting (:disc:`7976`)
- Fix resizing window when alternate screen is active does not preserve trailing blank output line in the main screen (:iss:`7978`)
- Wayland: Fix :opt:`background_opacity` less than one causing flicker on startup when the Wayland compositor supports single pixel buffers (:iss:`7987`)
- Fix background image flashing when closing a tab (:iss:`7999`)
- When running a kitten that modifies the kitty config file if no config file exists create a commented out default config file and then modify it (:iss:`7991`)
0.36.4 [2024-09-27]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Fix a regression in the previous release that caused window padding to be rendered opaque even when :opt:`background_opacity` is less than 1 (:iss:`7895`)
- Wayland GNOME: Fix a crash when using multiple monitors with different scales and starting on or moving to the monitor with lower scale (:iss:`7894`)
- macOS: Fix a regression in the previous release that caused junk to be rendered in font previews in the choose fonts kitten and crash on Intel macs (:iss:`7892`)
0.36.3 [2024-09-25]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- The option ``second_transparent_bg`` has been removed and replaced by :opt:`transparent_background_colors` which allows setting up to seven additional colors that will be transparent, with individual opacities per color (:iss:`7646`)
- Fix a regression in the previous release that broke use of the ``cd`` command in session files (:iss:`7829`)
- macOS: Fix shortcuts that become entries in the global menubar being reported as removed shortcuts in the debug output
- macOS: Fix :opt:`macos_option_as_alt` not working when :kbd:`caps lock` is engaged (:iss:`7836`)
- Fix a regression when tinting of background images was introduced that caused window borders to have :opt:`background_opacity` applied to them (:iss:`7850`)
- Fix a regression that broke writing to the clipboard using the OSC 5522 protocol (:iss:`7858`)
- macOS: Fix a regression in the previous release that caused kitty to fail to run after an unclean shutdown/crash when using --single-instance (:iss:`7846`)
- kitten @ ls: Fix the ``--self`` flag not working (:iss:`7864`)
- Remote control: Fix ``--match state:self`` not working (:disc:`7886`)
- Splits layout: Allow setting the ``split_axis`` option to ``auto`` so that all new windows have their split axis chosen automatically unless explicitly specified in the launch command (:iss:`7887`)
0.36.2 [2024-09-06]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Linux: Fix a regression in 0.36.0 that caused font features defined via fontconfig to be ignored (:iss:`7773`)
- :ac:`goto_tab`: Allow numbers less than ``-1`` to go to the Nth previously active tab
- Wayland: Fix for upcoming explicit sync changes in Wayland compositors breaking kitty (:iss:`7767`)
- Remote control: When listening on a UNIX domain socket only allow connections from processes having the same user id (:pull:`7777`)
- kitten @: Fix a regression connecting to TCP sockets using plain IP addresses rather than hostnames (:iss:`7794`)
- diff kitten: Fix a regression that broke diffing against remote files (:iss:`7797`)
0.36.1 [2024-08-24]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Allow specifying that the :opt:`cursor shape for unfocused windows <cursor_shape_unfocused>` should remain unchanged (:pull:`7728`)
- MacOS Intel: Fix a crash in the choose-fonts kitten when displaying previews of variable fonts (:iss:`7734`)
- Remote control: Fix a regression causing an escape code to leak when using @ launch with ``--no-response`` over the TTY (:iss:`7752`)
- OSC 52: Fix a regression in the previous release that broke handling of invalid base64 encoded data in OSC 52 requests (:iss:`7757`)
- macOS: Fix a regression in the previous release that caused :option:`kitty --single-instance` to not work when using :file:`macos-launch-services-cmdline`
0.36.0 [2024-08-17]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Support `OpenType Variable fonts <https://en.wikipedia.org/wiki/Variable_font>`__ (:iss:`3711`)
- A new :doc:`choose-fonts </kittens/choose-fonts>` kitten that provides a UI with font previews to ease selection of fonts. Also has support for font features and variable fonts
- Allow animating the blinking of the cursor. See :opt:`cursor_blink_interval` for how to configure it
- Add NERD fonts builtin so that users don't have to install them to use NERD symbols in kitty. The builtin font is used only if the symbols are not available in some system font
- launch command: A new :option:`launch --bias` option to adjust the size of newly created windows declaratively (:iss:`7634`)
- A new option :opt:`transparent_background_colors` to make a second background color semi-transparent via :opt:`background_opacity`. Useful for things like cursor line highlight in editors (:iss:`7646`)
- A new :doc:`notify </kittens/notify>` kitten to show desktop notifications
from the command line with support for icons, buttons and more.
- Desktop notifications protocol: Add support for icons, buttons, closing of notifications, expiry of notifications, updating of notifications and querying if the terminal emulator supports the protocol (:iss:`7657`, :iss:`7658`, :iss:`7659`)
- A new option :opt:`filter_notification` to filter out or perform arbitrary actions on desktop notifications based on sophisticated criteria (:iss:`7670`)
- A new protocol to allow terminal applications to change colors in the terminal more robustly than with the legacy XTerm protocol (:ref:`color_control`)
- Sessions: A new command ``focus_matching_window`` to shift focus to a specific window, useful when creating complex layouts with splits (:disc:`7635`)
- Speed up loading of large background images by caching the decoded image data. Also allow using images in JPEG/WEBP/TIFF/GIF/BMP formats in addition to PNG
- Wayland: Allow fractional scales less than one (:pull:`7549`)
- Wayland: Fix specifying the output name for the panel kitten not working (:iss:`7573`)
- icat kitten: Add an option :option:`kitty +kitten icat --no-trailing-newline` to leave the cursor to the right of the image (:iss:`7574`)
- Speed up ``kitty --version`` and ``kitty --single-instance`` (for all subsequent instances). They are now the fastest of all terminal emulators with similar functionality
- macOS: Fix rendering of the unicode hyphen (U+2010) character when using a font that does not include a glyph for it (:iss:`7525`)
- macOS 15: Handle Fn modifier when detecting global shortcuts (:iss:`7582`)
- Dispatch any clicks waiting for :opt:`click_interval` on key events (:iss:`7601`)
- ``kitten run-shell``: Automatically add the directory containing the kitten binary to PATH if needed. Controlled via the ``--inject-self-onto-path`` option (`disc`:7668`)
- Wayland: Fix an issue with mouse selections not being stopped when there are multiple OS windows (:iss:`7381`)
- Splits layout: Fix the ``move_to_screen_edge`` action breaking when only a single window is present (:iss:`7621`)
- Add support for in-band window resize notifications (:iss:`7642`)
- Allow controlling the easing curves used for :opt:`visual_bell_duration`
- New special rendering for font symbols useful in drawing commit graphs (:pull:`7681`)
- diff kitten: Add bindings to jump to next and previous file (:pull:`7683`)
- Wayland GNOME: Fix the font size in the OS Window title bar changing with the size of the text in the window (:disc:`7677`)
- Wayland GNOME: Fix a small rendering artifact when docking a window at a screen edge or maximizing it (:iss:`7701`)
- When :opt:`shell` is set to ``.`` respect the SHELL environment variable in the environment in which kitty is launched (:pull:`7714`)
- macOS: Bump the minimum required macOS version to Catalina released five years ago.
- Fix a regression in :opt:`notify_on_cmd_finish` that caused notifications to appear for every command after the first (:iss:`7725`)
0.35.2 [2024-06-22]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- A new option, :opt:`window_logo_scale` to specify how window logos are scaled with respect to the size of the window containing the logo (:pull:`7534`)
- A new option, :opt:`cursor_shape_unfocused` to specify the shape of the text cursor in unfocused OS windows (:pull:`7544`)
- Remote control: Fix empty password not working (:iss:`7538`)
- Wayland: Fix regression in 0.34.0 causing flickering on window resize on NVIDIA drivers (:iss:`7493`)
- Wayland labwc: Fix kitty timing out waiting for compositor to quit fucking around with scales on labwc (:iss:`7540`)
- Fix :opt:`scrollback_indicator_opacity` not actually controlling the opacity (:iss:`7557`)
- URL detection: Fix IPv6 hostnames breaking URL detection (:iss:`7565`)
0.35.1 [2024-05-31]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Wayland: Fix a regression in 0.34 that caused the tab bar to not render in second and subsequent OS Windows under Hyprland (:iss:`7413`)
- Fix a regression in the previous release that caused horizontal scrolling via touchpad in fullscreen applications to be reversed on non-Wayland platforms (:iss:`7475`, :iss:`7481`)
- Fix a regression in the previous release causing an error when setting background_opacity to zero (:iss:`7483`)
- Image display: Fix cursor movement and image hit region incorrect for image placements that specify only a number of rows or columns to display in (:iss:`7479`)
0.35.0 [2024-05-25]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- kitten @ run: A new remote control command to run a process on the machine kitty is running on and get its output (:iss:`7429`)
- :opt:`notify_on_cmd_finish`: Show the actual command that was finished (:iss:`7420`)
- hints kitten: Allow clicking on matched text to select it in addition to typing the hint
- Shell integration: Make the currently executing cmdline available as a window variable in kitty
- :opt:`paste_actions`: Fix ``replace-newline`` not working with ``confirm`` (:iss:`7374`)
- Graphics: Fix aspect ratio of images not being preserved when only a single
dimension of the destination rectangle is specified (:iss:`7380`)
- :ac:`focus_visible_window`: Fix selecting with mouse click leaving keyboard in unusable state (:iss:`7390`)
- Wayland: Fix infinite loop causing bad performance when using IME via fcitx5 due to a change in fcitx5 (:iss:`7396`)
- Desktop notifications protocol: Add support for specifying urgency
- Improve rendering of Unicode shade character to avoid Moire patterns (:pull:`7401`)
- kitten @ send-key: Fix some keys being sent in kitty keyboard protocol encoding when not using socket for remote control
- Dont clear selections on erase in screen commands unless the erased region intersects a selection (:iss:`7408`)
- Wayland: save energy by not rendering "suspended" windows on compositors that support that
- Allow more types of alignment for :opt:`placement_strategy` (:pull:`7419`)
- Add some more box-drawing characters from the "Geometric shapes" Unicode block (:iss:`7433`)
- Linux: Run all child processes in their own systemd scope to prevent the OOM killer from harvesting kitty when a child process misbehaves (:iss:`7427`)
- Mouse reporting: Fix horizontal scroll events inverted (:iss:`7439`)
- Remote control: @ action: Fix some actions being performed on the active window instead of the matched window (:iss:`7438`)
- Scrolling with mouse wheel when a selection is active should update the selection (:iss:`7453`)
- Fix kitten @ set-background-opacity limited to min opacity of 0.1 instead of 0 (:iss:`7463`)
- launch --hold: Fix hold not working if kernel signals process group with SIGINT (:iss:`7466`)
- macOS: Fix --start-as=fullscreen not working when another window is already fullscreen (:iss:`7448`)
- Add option :option:`kitten @ detach-window --stay-in-tab` to keep focus in the currently active tab when moving windows (:iss:`7468`)
- macOS: Fix changing window chrome/colors while in traditional fullscreen causing the titlebar to become visible (:iss:`7469`)
0.34.1 [2024-04-19]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Wayland KDE: Fix window background blur not adapting when window is grown. Also fix turning it on and off not working. (:iss:`7351`)
- Wayland GNOME: Draw the titlebar buttons without using a font (:iss:`7349`)
- Fix a regression in the previous release that caused incorrect font selection when using variable fonts on Linux (:iss:`7361`)
0.34.0 [2024-04-15]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Wayland: :doc:`panel kitten <kittens/panel>`: Add support for drawing desktop background and bars - Wayland: :doc:`panel kitten <kittens/panel>`: Add support for drawing desktop background and bars
using the panel kitten for all compositors that support the `requisite Wayland using the panel kitten for all compositors that support the requisite Wayland
protocol <https://wayland.app/protocols/wlr-layer-shell-unstable-v1>`__ which is practically speaking all of them but GNOME (:pull:`2590`) protocol which is practically speaking all of them but GNOME (:pull:`2590`)
- Show a small :opt:`scrollback indicator <scrollback_indicator_opacity>` along the right window edge when viewing - Show a small :opt:`scrollback indicator <scrollback_indicator_opacity>` along the right window edge when viewing
the scrollback to keep track of scroll position (:iss:`2502`) the scrollback to keep track of scroll position (:iss:`2502`)
- Wayland: Support fractional scales so that there is no wasted drawing at larger scale followed by resizing in the compositor - Wayland: Support fractional scales so that there is no wasted drawing at larger scale followed by resizing in the compositor
- Wayland: Support preferred integer scales
- Wayland KDE: Support :opt:`background_blur` - Wayland KDE: Support :opt:`background_blur`
- Wayland GNOME: The window titlebar now has buttons to minimize/maximize/close the window
- Wayland GNOME: The window titlebar color now follows the system light/dark color scheme preference, see :opt:`wayland_titlebar_color`
- Wayland KDE: Fix mouse cursor hiding not working in Plasma 6 (:iss:`7265`) - Wayland KDE: Fix mouse cursor hiding not working in Plasma 6 (:iss:`7265`)
- Wayland IME: Fix a bug with handling synthetic keypresses generated by ZMK keyboard + fcitx (:pull:`7283`) - Wayland IME: Fix a bug with handling synthetic keypresses generated by ZMK keyboard + fcitx (:pull:`7283`)
@@ -597,16 +83,6 @@ Detailed list of changes
- Linux: Fix for a regression in 0.32.0 that caused some CJK fonts to not render glyphs (:iss:`7263`) - Linux: Fix for a regression in 0.32.0 that caused some CJK fonts to not render glyphs (:iss:`7263`)
- Wayland: Support preferred integer scales
- Wayland: A new option :opt:`wayland_enable_ime` to turn off Input Method Extensions which add latency and create bugs
- Wayland: Fix :opt:`hide_window_decorations` not working on non GNOME desktops
- When asking for quit confirmation because of a running program, mention the program name (:iss:`7331`)
- Fix flickering of prompt during window resize (:iss:`7324`)
0.33.1 [2024-03-21] 0.33.1 [2024-03-21]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1770,7 +1246,7 @@ Detailed list of changes
- macOS: Fix keyboard input not working after toggling fullscreen till the - macOS: Fix keyboard input not working after toggling fullscreen till the
window is clicked in window is clicked in
- A new mappable action ``nth_os_window`` to focus the specified nth OS - A new mappable action ``nth_os_window`` to to focus the specified nth OS
window. (:pull:`4316`) window. (:pull:`4316`)
- macOS: The kitty window can be scrolled by the mouse wheel when OS window not - macOS: The kitty window can be scrolled by the mouse wheel when OS window not

View File

@@ -47,8 +47,7 @@ The terminal emulator will reply with a sequence of escape codes of the form::
Here, the ``status=DATA`` packets deliver the data (as base64 encoded bytes) Here, the ``status=DATA`` packets deliver the data (as base64 encoded bytes)
associated with each MIME type. The terminal emulator should chunk up the data associated with each MIME type. The terminal emulator should chunk up the data
for an individual type, into chunks of size **no more** than 4096 bytes (4096 for an individual type. A recommended size for each chunk is 4096 bytes. All
is the size of a chunk *before* base64 encoding). All
the chunks for a given type must be transmitted sequentially and only once they the chunks for a given type must be transmitted sequentially and only once they
are done the chunks for the next type, if any, should be sent. The end of data are done the chunks for the next type, if any, should be sent. The end of data
is indicated by a ``status=DONE`` packet. is indicated by a ``status=DONE`` packet.
@@ -91,8 +90,7 @@ following sequence of packets::
The final packet with no mime and no data indicates end of transmission. The The final packet with no mime and no data indicates end of transmission. The
data for every MIME type should be split into chunks of no more than 4096 data for every MIME type should be split into chunks of no more than 4096
bytes (4096 is the size of the data before base64 encoding). bytes. All the chunks for a given MIME type must be sent sequentially, before
All the chunks for a given MIME type must be sent sequentially, before
sending chunks for the next MIME type. After the transmission is complete, the sending chunks for the next MIME type. After the transmission is complete, the
terminal replies with a single packet indicating success:: terminal replies with a single packet indicating success::

View File

@@ -1,8 +1,5 @@
Color control
====================
Saving and restoring colors Saving and restoring colors
------------------------------ ==============================
It is often useful for a full screen application with its own color themes to It is often useful for a full screen application with its own color themes to
set the default foreground, background, selection and cursor colors and the ANSI set the default foreground, background, selection and cursor colors and the ANSI
@@ -27,157 +24,3 @@ foreground, selection background, selection foreground and cursor color and the
promoting interoperability, kitty added support for xterm's escape codes as promoting interoperability, kitty added support for xterm's escape codes as
well, and changed this extension to also save/restore the entire ANSI color well, and changed this extension to also save/restore the entire ANSI color
table. table.
.. _color_control:
Setting and querying colors
-------------------------------
While there exists a legacy protocol developed by XTerm for querying and
setting colors, as with most XTerm protocols it suffers from the usual design
limitations of being under specified and in-sufficient. XTerm implements
querying of colors using OSC 4,5,6,10-19,104,105,106,110-119. This absurd
profusion of numbers is completely unnecessary, redundant and requires adding
two new numbers for every new color. Also XTerm's protocol doesn't handle the
case of colors that are unknown to the terminal or that are not a set value,
for example, many terminals implement selection as a reverse video effect not a
fixed color. The XTerm protocol has no way to query for this condition. The
protocol also doesn't actually specify the format in which colors are reported,
deferring to a man page for X11!
Instead kitty has developed a single number based protocol that addresses all
these shortcomings and is future proof by virtue of using string keys rather
than numbers. The syntax of the escape code is::
<OSC> 21 ; key=value ; key=value ; ... <ST>
The spaces in the above definition are for reading clarity and should be ignored.
Here, ``<OSC>`` is the two bytes ``0x1b (ESC)`` and ``0x5d (])``. ``ST`` is
either ``0x7 (BEL)`` or the two bytes ``0x1b (ESC)`` and ``0x5c (\\)``.
``key`` is a number from 0-255 to query or set the color values from the
terminals ANSI color table, or one of the strings in the table below for
special colors:
================================= =============================================== ===============================
key meaning dynamic
================================= =============================================== ===============================
foreground The default foreground text color Not applicable
background The default background text color Not applicable
selection_background The background color of selections Reverse video
selection_foreground The foreground color of selections Reverse video
cursor The color of the text cursor Foreground color
cursor_text The color of text under the cursor Background color
visual_bell The color of a visual bell Automatic color selection based on current screen colors
transparent_background_color1..8 A background color that is rendered Unset
with the specified opacity in cells that have
the specified background color. An opacity
value less than zero means, use the
:opt:`background_opacity` value.
================================= =============================================== ===============================
In this table the third column shows what effect setting the color to *dynamic*
has in kitty and many other terminal emulators. It is advisory only, terminal
emulators may not support dynamic colors for these or they may have other
effects. Setting the ANSI color table colors to dynamic is not allowed.
Querying current color values
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To query colors values, the client program sends this escape code with the
``value`` field set to ``?`` (the byte ``0x3f``). The terminal then responds
with the same escape code, but with the ``?`` replaced by the :ref:`encoded
color value <color_control_color_encoding>`. If the queried color is one that
does not have a defined value, for example, if the terminal is using a reverse
video effect or a gradient or similar, then the value must be empty, that is
the response contains only the key and ``=``, no value. For example, if the
client sends::
<OSC> 21 ; foreground=? ; cursor=? <ST>
The terminal responds::
<OSC> 21 ; foreground=rgb:ff/00/00 ; cursor= <ST>
This indicates that the foreground color is red and the cursor color is
undefined (typically the cursor takes the color of the text under it and the
text takes the color of the background).
If the terminal does not know a field that a client send to it for a query it
must respond back with the ``field=?``, that is, it must send back a question
mark as the value.
Setting color values
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To set a color value, the client program sends this escape code with the
``value`` field set to either an :ref:`encoded color value
<color_control_color_encoding>` or the empty value. The empty value means
the terminal should use a dynamic color for example reverse video for
selections or similar. To reset a color to its default value (i.e. the value it
would have if it was never set) the client program should send just the key
name with no ``=`` and no value. For example::
<OSC> 21 ; foreground=green ; cursor= ; background <ST>
This sets the foreground to the color green, sets the cursor color to dynamic
(usually meaning the cursor takes the color of the text under it) and resets
the background color to its default value.
To check if setting succeeded, the client can simply query the color, in fact
the two can be combined into a single escape code, for example::
<OSC> 21 ; foreground=white ; foreground=? <ST>
The terminal will change the foreground color and reply with the new foreground
color.
.. _color_control_color_encoding:
Color value encoding
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The color encoding is inherited from the scheme used by XTerm, for
compatibility, but a sane, rigorously specified subset is chosen.
RGB colors are encoded in one of three forms:
``rgb:<red>/<green>/<blue>``
| <red>, <green>, <blue> := h | hh | hhh | hhhh
| h := single hexadecimal digits (case insignificant)
| Note that h indicates the value scaled in 4 bits, hh the value scaled in 8 bits,
hhh the value scaled in 12 bits, and hhhh the value scaled in 16 bits, respectively.
``#<h...>``
| h := single hexadecimal digits (case insignificant)
| #RGB (4 bits each)
| #RRGGBB (8 bits each)
| #RRRGGGBBB (12 bits each)
| #RRRRGGGGBBBB (16 bits each)
| The R, G, and B represent single hexadecimal digits. When fewer than 16 bits
each are specified, they represent the most significant bits of the value
(unlike the “rgb:” syntax, in which values are scaled). For example,
the string ``#3a7`` is the same as ``#3000a0007000``.
``rgbi:<red>/<green>/<blue>``
red, green, and blue are floating-point values between 0.0 and 1.0, inclusive. The input format for these values is an optional
sign, a string of numbers possibly containing a decimal point, and an optional exponent field containing an E or e followed by a possibly
signed integer string. Values outside the ``0 - 1`` range must be clipped to be within the range.
If a color should have an alpha component, it must be suffixed to the color
specification in the form :code:`@number between zero and one`. For example::
red@0.5 rgb:ff0000@0.1 #ff0000@0.3
The syntax for the floating point alpha component is the same as used for the
components of ``rgbi`` defined above. When not specified, the default alpha
value is ``1.0``. Values outside the range ``0 - 1`` must be clipped
to be within the range, negative values may have special context dependent
meaning.
In addition, the following color names are accepted (case-insensitively) corresponding to the
specified RGB values.
.. include:: generated/color-names.rst

View File

@@ -29,7 +29,7 @@ if kitty_src not in sys.path:
from kitty.conf.types import Definition, expand_opt_references # noqa from kitty.conf.types import Definition, expand_opt_references # noqa
from kitty.constants import str_version, website_url # noqa from kitty.constants import str_version, website_url # noqa
from kitty.fast_data_types import Shlex, TEXT_SIZE_CODE # noqa from kitty.fast_data_types import Shlex # noqa
# config {{{ # config {{{
# -- Project information ----------------------------------------------------- # -- Project information -----------------------------------------------------
@@ -119,7 +119,6 @@ def go_version(go_mod_path: str) -> str: # {{{
string_replacements = { string_replacements = {
'_kitty_install_cmd': 'curl -L https://sw.kovidgoyal.net/kitty/installer.sh | sh /dev/stdin', '_kitty_install_cmd': 'curl -L https://sw.kovidgoyal.net/kitty/installer.sh | sh /dev/stdin',
'_build_go_version': go_version('../go.mod'), '_build_go_version': go_version('../go.mod'),
'_text_size_code': str(TEXT_SIZE_CODE),
} }
@@ -283,29 +282,13 @@ if you specify a program-to-run you can use the special placeholder
p(f'\nThe source code for this kitten is `available on GitHub <{scurl}>`_.') p(f'\nThe source code for this kitten is `available on GitHub <{scurl}>`_.')
p('\nCommand Line Interface') p('\nCommand Line Interface')
p('-' * 72) p('-' * 72)
appname = f'kitten {kitten}'
if kitten in ('panel', 'broadcast', 'remote_file'):
appname = 'kitty +' + appname
p('\n\n' + option_spec_as_rst( p('\n\n' + option_spec_as_rst(
data['options'], message=data['help_text'], usage=data['usage'], appname=appname, heading_char='^')) data['options'], message=data['help_text'], usage=data['usage'], appname=f'kitty +kitten {kitten}',
heading_char='^'))
# }}} # }}}
def write_color_names_table() -> None: # {{{
from kitty.rgb import color_names
def s(c: Any) -> str:
return f'{c.red:02x}/{c.green:02x}/{c.blue:02x}'
with open('generated/color-names.rst', 'w') as f:
p = partial(print, file=f)
p('=' * 50, '=' * 20)
p('Name'.ljust(50), 'RGB value')
p('=' * 50, '=' * 20)
for name, col in color_names.items():
p(name.ljust(50), s(col))
p('=' * 50, '=' * 20)
# }}}
def write_remote_control_protocol_docs() -> None: # {{{ def write_remote_control_protocol_docs() -> None: # {{{
from kitty.rc.base import RemoteCommand, all_command_names, command_for_name from kitty.rc.base import RemoteCommand, all_command_names, command_for_name
field_pat = re.compile(r'\s*([^:]+?)\s*:\s*(.+)') field_pat = re.compile(r'\s*([^:]+?)\s*:\s*(.+)')
@@ -334,10 +317,8 @@ def write_remote_control_protocol_docs() -> None: # {{{
else: else:
title = f'{title} (optional)' title = f'{title} (optional)'
p(f':code:`{title}`') p(f':code:`{title}`')
p(' ', desc) p(' ', desc), p()
p() p(), p()
p()
p()
with open('generated/rc.rst', 'w') as f: with open('generated/rc.rst', 'w') as f:
p = partial(print, file=f) p = partial(print, file=f)
@@ -720,7 +701,7 @@ def setup_man_pages() -> None:
base = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) base = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
for x in glob.glob(os.path.join(base, 'docs/kittens/*.rst')): for x in glob.glob(os.path.join(base, 'docs/kittens/*.rst')):
kn = os.path.basename(x).rpartition('.')[0] kn = os.path.basename(x).rpartition('.')[0]
if kn in ('custom', 'developing-builtin-kittens'): if kn == 'custom':
continue continue
cd = get_kitten_cli_docs(kn) or {} cd = get_kitten_cli_docs(kn) or {}
khn = kn.replace('_', '-') khn = kn.replace('_', '-')
@@ -767,7 +748,6 @@ def setup(app: Any) -> None:
kn = all_kitten_names() kn = all_kitten_names()
write_cli_docs(kn) write_cli_docs(kn)
write_remote_control_protocol_docs() write_remote_control_protocol_docs()
write_color_names_table()
write_conf_docs(app, kn) write_conf_docs(app, kn)
app.add_config_value('string_replacements', {}, True) app.add_config_value('string_replacements', {}, True)
app.connect('source-read', replace_string) app.connect('source-read', replace_string)

View File

@@ -13,11 +13,11 @@ kitty.conf
|kitty| is highly customizable, everything from keyboard shortcuts, to rendering |kitty| is highly customizable, everything from keyboard shortcuts, to rendering
frames-per-second. See below for an overview of all customization possibilities. frames-per-second. See below for an overview of all customization possibilities.
You can open the config file within |kitty| by pressing :sc:`edit_config_file` You can open the config file within kitty by pressing :sc:`edit_config_file`
(:kbd:`⌘+,` on macOS). A :file:`kitty.conf` with commented default (:kbd:`⌘+,` on macOS). A :file:`kitty.conf` with commented default
configurations and descriptions will be created if the file does not exist. configurations and descriptions will be created if the file does not exist.
You can reload the config file within |kitty| by pressing You can reload the config file within kitty by pressing
:sc:`reload_config_file` (:kbd:`⌃+⌘+,` on macOS) or sending |kitty| the :sc:`reload_config_file` (:kbd:`⌃+⌘+,` on macOS) or sending kitty the
``SIGUSR1`` signal with ``kill -SIGUSR1 $KITTY_PID``. You can also display the ``SIGUSR1`` signal with ``kill -SIGUSR1 $KITTY_PID``. You can also display the
current configuration by pressing :sc:`debug_config` (:kbd:`⌥+⌘+,` on macOS). current configuration by pressing :sc:`debug_config` (:kbd:`⌥+⌘+,` on macOS).
@@ -28,16 +28,16 @@ current configuration by pressing :sc:`debug_config` (:kbd:`⌥+⌘+,` on macOS)
:option:`kitty --config` option or use the :envvar:`KITTY_CONFIG_DIRECTORY` :option:`kitty --config` option or use the :envvar:`KITTY_CONFIG_DIRECTORY`
environment variable. See :option:`kitty --config` for full details. environment variable. See :option:`kitty --config` for full details.
**Comments** can be added to the config file as lines starting with the ``#`` Comments can be added to the config file as lines starting with the ``#``
character. This works only if the ``#`` character is the first character in the character. This works only if the ``#`` character is the first character in the
line. line.
**Lines can be split** by starting the next line with the ``\`` character. Lines can be split by starting the next line with the ``\`` character.
All leading whitespace and the ``\`` character are removed. All leading whitespace and the ``\`` character are removed.
.. _include: .. _include:
You can **include secondary config files** via the :code:`include` directive. If You can include secondary config files via the :code:`include` directive. If
you use a relative path for :code:`include`, it is resolved with respect to the you use a relative path for :code:`include`, it is resolved with respect to the
location of the current config file. Note that environment variables are location of the current config file. Note that environment variables are
expanded, so :code:`${USER}.conf` becomes :file:`name.conf` if expanded, so :code:`${USER}.conf` becomes :file:`name.conf` if
@@ -45,21 +45,13 @@ expanded, so :code:`${USER}.conf` becomes :file:`name.conf` if
to detect the operating system. It is ``linux``, ``macos`` or ``bsd``. to detect the operating system. It is ``linux``, ``macos`` or ``bsd``.
Also, you can use :code:`globinclude` to include files Also, you can use :code:`globinclude` to include files
matching a shell glob pattern and :code:`envinclude` to include configuration matching a shell glob pattern and :code:`envinclude` to include configuration
from environment variables. Finally, you can dynamically generate configuration from environment variables. For example::
by running a program using :code:`geninclude`. For example::
# Include other.conf
include other.conf include other.conf
# Include *.conf files from all subdirs of kitty.d inside the kitty config dir # Include *.conf files from all subdirs of kitty.d inside the kitty config dir
globinclude kitty.d/**/*.conf globinclude kitty.d/**/*.conf
# Include the *contents* of all env vars starting with KITTY_CONF_ # Include the *contents* of all env vars starting with KITTY_CONF_
envinclude KITTY_CONF_* envinclude KITTY_CONF_*
# Run the script dynamic.py placed in the same directory as this config file
# and include its :file:`STDOUT`. Note that Python scripts are fastest
# as they use the embedded Python interpreter, but any executable script
# or program is supported, in any language. Remember to mark the script
# file executable.
geninclude dynamic.py
.. note:: Syntax highlighting for :file:`kitty.conf` in vim is available via .. note:: Syntax highlighting for :file:`kitty.conf` in vim is available via
@@ -95,7 +87,7 @@ This will print the commented out default config file to :file:`STDOUT`.
All mappable actions All mappable actions
------------------------ ------------------------
See the :doc:`list of all the things you can make |kitty| can do </actions>`. See the :doc:`list of all the things you can make kitty can do </actions>`.
.. toctree:: .. toctree::
:hidden: :hidden:

View File

@@ -33,16 +33,7 @@ notification from a shell script::
To show a message with a title and a body:: To show a message with a title and a body::
printf '\x1b]99;i=1:d=0;Hello world\x1b\\' printf '\x1b]99;i=1:d=0;Hello world\x1b\\'
printf '\x1b]99;i=1:p=body;This is cool\x1b\\' printf '\x1b]99;i=1:d=1:p=body;This is cool\x1b\\'
.. tip::
|kitty| also comes with its own :doc:`statically compiled command line tool </kittens/notify>` to easily display
notifications, with all their advanced features. For example:
.. code-block:: sh
kitten notify "Hello world" A good day to you
The most important key in the metadata is the ``p`` key, it controls how the The most important key in the metadata is the ``p`` key, it controls how the
payload is interpreted. A value of ``title`` means the payload is setting the payload is interpreted. A value of ``title`` means the payload is setting the
@@ -52,55 +43,20 @@ and so on, see the table below for full details.
The design of the escape code is fundamentally chunked, this is because The design of the escape code is fundamentally chunked, this is because
different terminal emulators have different limits on how large a single escape different terminal emulators have different limits on how large a single escape
code can be. Chunking is accomplished by the ``i`` and ``d`` keys. The ``i`` code can be. Chunking is accomplished by the ``i`` and ``d`` keys. The ``i``
key is the *notification id* which is an :ref:`identifier`. key is the *notification id* which can be any string containing the characters
The ``d`` key stands for *done* and can only take the ``[a-zA-Z0-9_-+.]``. The ``d`` key stands for *done* and can only take the
values ``0`` and ``1``. A value of ``0`` means the notification is not yet done values ``0`` and ``1``. A value of ``0`` means the notification is not yet done
and the terminal emulator should hold off displaying it. A non-zero value means and the terminal emulator should hold off displaying it. A value of ``1`` means
the notification is done, and should be displayed. You can specify the title or the notification is done, and should be displayed. You can specify the title or
body multiple times and the terminal emulator will concatenate them, thereby body multiple times and the terminal emulator will concatenate them, thereby
allowing arbitrarily long text (terminal emulators are free to impose a sensible allowing arbitrarily long text (terminal emulators are free to impose a sensible
limit to avoid Denial-of-Service attacks). The size of the payload must be no limit to avoid Denial-of-Service attacks). The size of the payload must be no
longer than ``2048`` bytes, *before being encoded* or ``4096`` encoded bytes. longer than ``2048`` bytes, *before being encoded*.
Both the ``title`` and ``body`` payloads must be either :ref:`safe_utf8` text Both the ``title`` and ``body`` payloads must be either UTF-8 encoded plain
or UTF-8 text that is :ref:`base64` encoded, in which case there must be an text with no embedded escape codes, or UTF-8 text that is Base64 encoded, in
``e=1`` key in the metadata to indicate the payload is :ref:`base64` which case there must be an ``e=1`` key in the metadata to indicate the payload
encoded. No HTML or other markup in the plain text is allowed. It is strictly is Base64 encoded.
plain text, to be interpreted as such.
Allowing users to filter notifications
-------------------------------------------------------
.. versionadded:: 0.36.0
Specifying application name and notification type
Well behaved applications should identify themselves to the terminal
by means of two keys ``f`` which is the application name and ``t``
which is the notification type. These are free form keys, they can contain
any values, their purpose is to allow users to easily filter out
notifications they do not want. Both keys must have :ref:`base64`
encoded UTF-8 text as their values. The ``t`` key can be specified multiple
times, as notifications can have more than one type. See the `freedesktop.org
spec
<https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html#categories>`__
for examples of notification types.
.. note::
The application name should generally be set to the filename of the
applications `desktop file
<https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#file-naming>`__
(without the ``.desktop`` part) or the bundle identifier for a macOS
application. While not strictly necessary, this allows the terminal
emulator to deduce an icon for the notification when one is not specified.
.. tip::
|kitty| has sophisticated notification filtering and management
capabilities via :opt:`filter_notification`.
Being informed when user activates the notification
-------------------------------------------------------
When the user clicks the notification, a couple of things can happen, the When the user clicks the notification, a couple of things can happen, the
terminal emulator can focus the window from which the notification came, and/or terminal emulator can focus the window from which the notification came, and/or
@@ -115,319 +71,29 @@ escape code is::
The value of ``identifier`` comes from the ``i`` key in the escape code sent by The value of ``identifier`` comes from the ``i`` key in the escape code sent by
the application. If the application sends no identifier, then the terminal the application. If the application sends no identifier, then the terminal
*must* use ``i=0``. (Ideally ``i`` should have been left out from the response, *must* use ``i=0``. Actions can be preceded by a negative sign to turn them
but for backwards compatibility ``i=0`` is used). Actions can be preceded by a off, so for example if you do not want any action, turn off the default
negative sign to turn them off, so for example if you do not want any action, ``focus`` action with::
turn off the default ``focus`` action with::
a=-focus a=-focus
Complete specification of all the metadata keys is in the :ref:`table below <keys_in_notificatons_protocol>`. Complete specification of all the metadata keys is in the table below. If a
If a terminal emulator encounters a key in the metadata it does not understand, terminal emulator encounters a key in the metadata it does not understand,
the key *must* be ignored, to allow for future extensibility of this escape the key *must* be ignored, to allow for future extensibility of this escape
code. Similarly if values for known keys are unknown, the terminal emulator code. Similarly if values for known keys are unknown, the terminal emulator
*should* either ignore the entire escape code or perform a best guess effort to *should* either ignore the entire escape code or perform a best guess effort
display it based on what it does understand. to display it based on what it does understand.
Being informed when a notification is closed
------------------------------------------------
.. versionadded:: 0.36.0
Notifications of close events
If you wish to be informed when a notification is closed, you can specify
``c=1`` when sending the notification. For example::
<OSC> 99 ; i=mynotification : c=1 ; hello world <terminator>
Then, the terminal will send the following
escape code to inform when the notification is closed::
<OSC> 99 ; i=mynotification : p=close ; <terminator>
If no notification id was specified ``i=0`` will be used in the response
If ``a=report`` is specified and the notification is activated/clicked on
then both the activation report and close notification are sent. If the notification
is updated then the close event is not sent unless the updated notification
also requests a close notification.
Note that on some platforms, such as macOS, the OS does not inform applications
when notifications are closed, on such platforms, terminals reply with::
<OSC> 99 ; i=mynotification : p=close ; untracked <terminator>
This means that the terminal has no way of knowing when the notification is
closed. Instead, applications can poll the terminal to determine which
notifications are still alive (not closed), with::
<OSC> 99 ; i=myid : p=alive ; <terminator>
The terminal will reply with::
<OSC> 99 ; i=myid : p=alive ; id1,id2,id3 <terminator>
Here, ``myid`` is present for multiplexer support. The response from the terminal
contains a comma separated list of ids that are still alive.
Updating or closing an existing notification
----------------------------------------------
.. versionadded:: 0.36.0
The ability to update and close a previous notification
To update a previous notification simply send a new notification with the same
*notification id* (``i`` key) as the one you want to update. If the original
notification is still displayed it will be replaced, otherwise a new
notification is displayed. This can be used, for example, to show progress of
an operation. How smoothly the existing notification is replaced
depends on the underlying OS, for example, on Linux the replacement is usually flicker
free, on macOS it isn't, because of Apple's design choices.
Note that if no ``i`` key is specified, no updating must take place, even if
there is a previous notification without an identifier. The terminal must
treat these as being two unique *unidentified* notifications.
To close a previous notification, send::
<OSC> i=<notification id> : p=close ; <terminator>
This will close a previous notification with the specified id. If no such
notification exists (perhaps because it was already closed or it was activated)
then the request is ignored. If no ``i`` key is specified, this must be a no-op.
Automatically expiring notifications
-------------------------------------
A notification can be marked as expiring (being closed) automatically after
a specified number of milliseconds using the ``w`` key. The default if
unspecified is ``-1`` which means to use whatever expiry policy the OS has for
notifications. A value of ``0`` means the notification should never expire.
Values greater than zero specify the number of milliseconds after which the
notification should be auto-closed. Note that the value of ``0``
is best effort, some platforms honor it and some do not. Positive values
are robust, since they can be implemented by the terminal emulator itself,
by manually closing the notification after the expiry time. The notification
could still be closed before the expiry time by user interaction or OS policy,
but it is guaranteed to be closed once the expiry time has passed.
Adding icons to notifications
--------------------------------
.. versionadded:: 0.36.0
Custom icons in notifications
Applications can specify a custom icon to be displayed with a notification.
This can be the application's logo or a symbol such as error or warning
symbols. The simplest way to specify an icon is by *name*, using the ``n``
key. The value of this key is :ref:`base64` encoded UTF-8 text. Names
can be either application names, or symbol names. The terminal emulator
will try to resolve the name based on icons and applications available
on the computer it is running on. The following list of well defined names
must be supported by any terminal emulator implementing this spec.
The ``n`` key can be specified multiple times, the terminal will go through
the list in order and use the first icon that it finds available on the
system.
.. table:: Universally available icon names
======================== ==============================================
Name Description
======================== ==============================================
``error`` An error symbol
``warn``, ``warning`` A warning symbol
``info`` A symbol denoting an informational message
``question`` A symbol denoting asking the user a question
``help`` A symbol denoting a help message
``file-manager`` A symbol denoting a generic file manager application
``system-monitor`` A symbol denoting a generic system monitoring/information application
``text-editor`` A symbol denoting a generic text editor application
======================== ==============================================
If an icon name is an application name it should be an application identifier,
such as the filename of the application's :file:`.desktop` file on Linux or its
bundle identifier on macOS. For example if the cross-platform application
FooBar has a desktop file named: :file:`foo-bar.desktop` and a bundle
identifier of ``net.foo-bar-website.foobar`` then it should use the icon names
``net.foo-bar-website.foobar`` *and* ``foo-bar`` so that terminals running on
both platforms can find the application icon.
If no icon is specified, but the ``f`` key (application name) is specified, the
terminal emulator should use the value of the ``f`` key to try to find a
suitable icon.
Adding icons by transmitting icon data
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This can be done by using the ``p=icon`` key. Then, the payload is the icon
image in any of the ``PNG``, ``JPEG`` or ``GIF`` image formats. It is recommended
to use an image size of ``256x256`` for icons. Since icons are binary data,
they must be transmitted encoded, with ``e=1``.
When both an icon name and an image are specified, the terminal emulator must
first try to find a locally available icon matching the name and only if one
is not found, fallback to the provided image. This is so that users are
presented with icons from their current icon theme, where possible.
Transmitted icon data can be cached using the ``g`` key. The value of the ``g``
key must be a random globally unique UUID like :ref:`identifier`. Then, the
terminal emulator will cache the transmitted data using that key. The cache
should exist for as long as the terminal emulator remains running. Thus, in
future notifications, the application can simply send the ``g`` key to display
a previously cached icon image with needing to re-transmit the actual data with
``p=icon``. The ``g`` key refers only to the icon data, multiple different
notifications with different icon or application names can use the same ``g``
key to refer to the same icon. Terminal multiplexers must cache icon data
themselves and refresh it in the underlying terminal implementation when
detaching and then re-attaching. This means that applications once started
need to transmit icon data only once until they are quit.
.. note:: .. note::
To avoid DoS attacks terminal implementations can impose a reasonable max size It is possible to extend this escape code to allow specifying an icon for
on the icon cache and evict icons in order of last used. Thus theoretically, the notification, however, given that some platforms, such as legacy versions
a previously cached icon may become unavailable, but given that icons are of macOS, don't allow displaying custom images on a notification, it was
small images, practically this is not an issue in all but the most resource decided to leave it out of the spec for the time being.
constrained environments, and the failure mode is simply that the icon is not
displayed.
.. note:: Similarly, features such as scheduled notifications could be added in future
How the icon is displayed depends on the underlying OS notifications revisions.
implementation. For example, on Linux, typically a single icon is displayed.
On macOS, both the terminal emulator's icon and the specified custom icon
are displayed.
Adding buttons to the notification
---------------------------------------
Buttons can be added to the notification using the *buttons* payload, with ``p=buttons``.
Buttons are a list of UTF-8 text separated by the Unicode Line Separator
character (U+2028) which is the UTF-8 bytes ``0xe2 0x80 0xa8``. They can be
sent either as :ref:`safe_utf8` or :ref:`base64`. When the user clicks on one
of the buttons, and reporting is enabled with ``a=report``, the terminal will
send an escape code of the form::
<OSC> 99 ; i=identifier ; button_number <terminator>
Here, `button_number` is a number from 1 onwards, where 1 corresponds
to the first button, two to the second and so on. If the user activates the
notification as a whole, and not a specific button, the response, as described
above is::
<OSC> 99 ; i=identifier ; <terminator>
If no identifier was specified when creating the notification, ``i=0`` is used.
The terminal *must not* send a response unless report is requested with
``a=report``.
.. note::
The appearance of the buttons depends on the underlying OS implementation.
On most Linux systems, the buttons appear as individual buttons on the
notification. On macOS they appear as a drop down menu that is accessible
when hovering the notification. Generally, using more than two or three
buttons is not a good idea.
.. _notifications_query:
Playing a sound with notifications
-----------------------------------------
.. versionadded:: 0.36.0
The ability to control the sound played with notifications
By default, notifications may or may not have a sound associated with them
depending on the policies of the OS notifications service. Sometimes it
might be useful to ensure a notification is not accompanied by a sound.
This can be done by using the ``s`` key which accepts :ref:`base64` encoded
UTF-8 text as its value. The set of known sounds names is in the table below,
any other names are implementation dependent, for instance, on Linux, terminal emulators will
probably support the `standard sound names
<https://specifications.freedesktop.org/sound-naming-spec/latest/#names>`__
.. table:: Standard sound names
======================== ==============================================
Name Description
======================== ==============================================
``system`` The default system sound for a notification, which may be some kind of beep or just silence
``silent`` No sound must accompany the notification
``error`` A sound associated with error messages
``warn``, ``warning`` A sound associated with warning messages
``info`` A sound associated with information messages
``question`` A sound associated with questions
======================== ==============================================
Support for sound names can be queried as described below.
Querying for support
-------------------------
.. versionadded:: 0.36.0
The ability to query for support
An application can query the terminal emulator for support of this protocol, by
sending the following escape code::
<OSC> 99 ; i=<some identifier> : p=? ; <terminator>
A conforming terminal must respond with an escape code of the form::
<OSC> 99 ; i=<some identifier> : p=? ; key=value : key=value <terminator>
The identifier is present to support terminal multiplexers, so that they know
which window to redirect the query response too.
Here, the ``key=value`` parts specify details about what the terminal
implementation supports. Currently, the following keys are defined:
======= ================================================================================
Key Value
======= ================================================================================
``a`` Comma separated list of actions from the ``a`` key that the terminal
implements. If no actions are supported, the ``a`` key must be absent from the
query response.
``c`` ``c=1`` if the terminal supports close events, otherwise the ``c``
must be omitted.
``o`` Comma separated list of occassions from the ``o`` key that the
terminal implements. If no occasions are supported, the value
``o=always`` must be sent in the query response.
``p`` Comma separated list of supported payload types (i.e. values of the
``p`` key that the terminal implements). These must contain at least
``title``.
``s`` Comma separated list of sound names from the table of standard sound names above.
Terminals will report the list of standard sound names they support.
Terminals *should* support atleast ``system`` and ``silent``.
``u`` Comma separated list of urgency values that the terminal implements.
If urgency is not supported, the ``u`` key must be absent from the
query response.
``w`` ``w=1`` if the terminal supports auto expiring of notifications.
======= ================================================================================
In the future, if this protocol expands, more keys might be added. Clients must
ignore keys they do not understand in the query response.
To check if a terminal emulator supports this notifications protocol the best way is to
send the above *query action* followed by a request for the `primary device
attributes <https://vt100.net/docs/vt510-rm/DA1.html>`_. If you get back an
answer for the device attributes without getting back an answer for the *query
action* the terminal emulator does not support this notifications protocol.
.. _keys_in_notificatons_protocol:
Specification of all keys used in the protocol
--------------------------------------------------
======= ==================== ========== ================= ======= ==================== ========== =================
Key Value Default Description Key Value Default Description
======= ==================== ========== ================= ======= ==================== ========== =================
@@ -437,30 +103,16 @@ Key Value Default Description
optional leading optional leading
``-`` ``-``
``c`` ``0`` or ``1`` ``0`` When non-zero an escape code is sent to the application when the notification is closed.
``d`` ``0`` or ``1`` ``1`` Indicates if the notification is ``d`` ``0`` or ``1`` ``1`` Indicates if the notification is
complete or not. A non-zero value complete or not.
means it is complete.
``e`` ``0`` or ``1`` ``0`` If set to ``1`` means the payload is :ref:`base64` encoded UTF-8, ``e`` ``0`` or ``1`` ``0`` If set to ``1`` means the payload is Base64 encoded UTF-8,
otherwise it is plain UTF-8 text with no C0 control codes in it otherwise it is plain UTF-8 text with no C0 control codes in it
``f`` :ref:`base64` ``unset`` The name of the application sending the notification. Can be used to filter out notifications. ``i`` ``[a-zA-Z0-9-_+.]`` ``0`` Identifier for the notification
encoded UTF-8
application name
``g`` :ref:`identifier` ``unset`` Identifier for icon data. Make these globally unqiue, ``p`` One of ``title`` or ``title`` Whether the payload is the notification title or body. If a
like an UUID. ``body``. notification has no title, the body will be used as title.
``i`` :ref:`identifier` ``unset`` Identifier for the notification. Make these globally unqiue,
like an UUID, so that terminal multiplexers can
direct responses to the correct window. Note that for backwards
compatibility reasons i=0 is special and should not be used.
``n`` :ref:`base64` ``unset`` Icon name. Can be specified multiple times.
encoded UTF-8
application name
``o`` One of ``always``, ``always`` When to honor the notification request. ``unfocused`` means when the window ``o`` One of ``always``, ``always`` When to honor the notification request. ``unfocused`` means when the window
``unfocused`` or the notification is sent on does not have keyboard focus. ``invisible`` ``unfocused`` or the notification is sent on does not have keyboard focus. ``invisible``
@@ -468,84 +120,15 @@ Key Value Default Description
and not visible to the user, for example, because it is in an inactive tab or and not visible to the user, for example, because it is in an inactive tab or
its OS window is not currently active. its OS window is not currently active.
``always`` is the default and always honors the request. ``always`` is the default and always honors the request.
``p`` One of ``title``, ``title`` Type of the payload. If a notification has no title, the body will be used as title.
``body``, A notification with not title and no body is ignored. Terminal
``close``, emulators should ignore payloads of unknown type to allow for future
``icon``, expansion of this protocol.
``?``, ``alive``,
``buttons``
``s`` :ref:`base64` ``system`` The sound name to play with the notification. ``silent`` means no sound.
encoded sound ``system`` means to play the default sound, if any, of the platform notification service.
name Other names are implementation dependent.
``t`` :ref:`base64` ``unset`` The type of the notification. Used to filter out notifications. Can be specified multiple times.
encoded UTF-8
notification type
``u`` ``0, 1 or 2`` ``unset`` The *urgency* of the notification. ``0`` is low, ``1`` is normal and ``2`` is critical.
If not specified normal is used.
``w`` ``>=-1`` ``-1`` The number of milliseconds to auto-close the notification after.
======= ==================== ========== ================= ======= ==================== ========== =================
.. versionadded:: 0.35.0 .. note::
Support for the ``u`` key to specify urgency
.. versionadded:: 0.31.0
Support for the ``o`` key to prevent notifications from focused windows Support for the ``o`` key to prevent notifications from focused windows
was added in kitty version 0.31.0
.. note:: .. note::
|kitty| also supports the `legacy OSC 9 protocol developed by iTerm2 |kitty| also supports the `legacy OSC 9 protocol developed by iTerm2
<https://iterm2.com/documentation-escape-codes.html>`__ for desktop <https://iterm2.com/documentation-escape-codes.html>`__ for desktop
notifications. notifications.
.. _base64:
Base64
---------------
The base64 encoding used in the this specification is the one defined in
:rfc:`4648`. When a base64 payload is chunked, either the chunking should be
done before encoding or after. When the chunking is done before encoding, no
more than 2048 bytes of data should be encoded per chunk and the encoded data
**must** include the base64 padding bytes, if any. When the chunking is done
after encoding, each encoded chunk must be no more than 4096 bytes in size.
There may or may not be padding bytes at the end of the last chunk, terminals
must handle either case.
.. _safe_utf8:
Escape code safe UTF-8
--------------------------
This must be valid UTF-8 as per the spec in :rfc:`3629`. In addition, in order
to make it safe for transmission embedded inside an escape code, it must
contain none of the C0 and C1 control characters, that is, the Unicode
characters: U+0000 (NUL) - U+1F (Unit separator), U+7F (DEL) and U+80 (PAD) - U+9F
(APC). Note that in particular, this means that no newlines, carriage returns,
tabs, etc. are allowed.
.. _identifier:
Identifier
----------------
Any string consisting solely of characters from the set ``[a-zA-Z0-9_-+.]``,
that is, the letters ``a-z``, ``A-Z``, the underscore, the hyphen, the plus
sign and the period. Applications should make these globally unique, like a
UUID for maximum robustness.
.. important::
Terminals **must** sanitize ids received from client programs before sending
them back in responses, to mitigate input injection based attacks. That is, they must
either reject ids containing characters not from the above set, or remove
bad characters when reading ids sent to them.

View File

@@ -23,26 +23,10 @@ these characters are followed by a space or en-space (U+2002) in which case
kitty makes use of the extra cell to render them in two cells. This behavior kitty makes use of the extra cell to render them in two cells. This behavior
can be turned off for specific symbols using :opt:`narrow_symbols`. can be turned off for specific symbols using :opt:`narrow_symbols`.
As of version 0.40 kitty has innovated a :doc:`new protocol
<text-sizing-protocol>` that allows programs running in the terminal to control
how many cells a character is rendered in thereby solving the issue of
character width once and for all.
Similarly, some monospaced font families are buggy and have bold or italic
faces that have characters wider than the width of the normal face, these
will also result in clipping. Such issues should be reported to the font
developer. Monospaced font families must have all their characters rendered
within a fixed width across all faces of the font, otherwise they aren't really
monospaced.
Using a color theme with a background color does not work well in vim? Using a color theme with a background color does not work well in vim?
----------------------------------------------------------------------- -----------------------------------------------------------------------
First, be sure to `use a color scheme in vim <https://github.com/kovidgoyal/kitty/discussions/8196#discussioncomment-11739991>`__
instead of relying on the terminal theme. Otherwise, background and text selection colours
may be difficult to read.
Sadly, vim has very poor out-of-the-box detection for modern terminal features. Sadly, vim has very poor out-of-the-box detection for modern terminal features.
Furthermore, it `recently broke detection even more <https://github.com/vim/vim/issues/11729>`__. Furthermore, it `recently broke detection even more <https://github.com/vim/vim/issues/11729>`__.
It kind of, but not really, supports terminfo, except it overrides it with its own hard-coded It kind of, but not really, supports terminfo, except it overrides it with its own hard-coded
@@ -167,7 +151,7 @@ I cannot use the key combination X in program Y?
First, run:: First, run::
kitten show-key -m kitty kitten show_key -m kitty
Press the key combination X. If the kitten reports the key press Press the key combination X. If the kitten reports the key press
that means kitty is correctly sending the key press to terminal programs. that means kitty is correctly sending the key press to terminal programs.
@@ -273,30 +257,27 @@ fonts to be freely resizable, so it does not support bitmapped fonts.
.. note:: .. note::
If you are trying to use a font patched with `Nerd Fonts If you are trying to use a font patched with `Nerd Fonts
<https://nerdfonts.com/>`__ symbols, don't do that as patching destroys <https://nerdfonts.com/>`__ symbols, don't do that as patching destroys
fonts. There is no need, kitty has a builtin NERD font and will use it for fonts. There is no need, simply install the standalone ``Symbols Nerd Font Mono``
symbols not found in any other font on your system. (the file :file:`NerdFontsSymbolsOnly.zip` from the `Nerd Fonts releases page
If you have patched fonts on your system they might be used instead for NERD <https://github.com/ryanoasis/nerd-fonts/releases>`__). kitty should pick up
symbols, so to force kitty to use the pure NERD font for NERD symbols, symbols from it automatically, and you can tell it to do so explicitly in
add the following line to :file:`kitty.conf`:: case it doesn't with the :opt:`symbol_map` directive::
# Nerd Fonts v3.3.0 # Nerd Fonts v3.1.0
symbol_map U+e000-U+e00a,U+ea60-U+ebeb,U+e0a0-U+e0c8,U+e0ca,U+e0cc-U+e0d7,U+e200-U+e2a9,U+e300-U+e3e3,U+e5fa-U+e6b7,U+e700-U+e8ef,U+ed00-U+efc1,U+f000-U+f2ff,U+f000-U+f2e0,U+f300-U+f381,U+f400-U+f533,U+f0001-U+f1af0 Symbols Nerd Font Mono symbol_map U+e000-U+e00a,U+ea60-U+ebeb,U+e0a0-U+e0c8,U+e0ca,U+e0cc-U+e0d4,U+e200-U+e2a9,U+e300-U+e3e3,U+e5fa-U+e6b1,U+e700-U+e7c5,U+f000-U+f2e0,U+f300-f372,U+f400-U+f532,U+f0001-U+f1af0 Symbols Nerd Font Mono
Those Unicode symbols not in the `Unicode private use areas Those Unicode symbols not in the `Unicode private use areas
<https://en.wikipedia.org/wiki/Private_Use_Areas>`__ are <https://en.wikipedia.org/wiki/Private_Use_Areas>`__ are
not included. not included.
If your font is not listed in ``kitten choose-fonts`` it means that it is not If your font is not listed in ``kitty +list-fonts`` it means that it is not
monospace or is a bitmapped font. On Linux you can list all monospace fonts monospace or is a bitmapped font. On Linux you can list all monospace fonts
with:: with::
fc-list : family spacing outline scalable | grep -e spacing=100 -e spacing=90 | grep -e outline=True | grep -e scalable=True fc-list : family spacing outline scalable | grep -e spacing=100 -e spacing=90 | grep -e outline=True | grep -e scalable=True
On macOS, you can open *Font Book* and look in the :guilabel:`Fixed width` Note that the spacing property is calculated by fontconfig based on actual glyph
collection to see all monospaced fonts on your system.
Note that **on Linux**, the spacing property is calculated by fontconfig based on actual glyph
widths in the font. If for some reason fontconfig concludes your favorite widths in the font. If for some reason fontconfig concludes your favorite
monospace font does not have ``spacing=100`` you can override it by using the monospace font does not have ``spacing=100`` you can override it by using the
following :file:`~/.config/fontconfig/fonts.conf`:: following :file:`~/.config/fontconfig/fonts.conf`::
@@ -319,7 +300,7 @@ command to rebuild your fontconfig cache::
fc-cache -r fc-cache -r
Then, the font will be available in ``kitten choose-fonts``. Then, the font will be available in ``kitty +list-fonts``.
How can I assign a single global shortcut to bring up the kitty terminal? How can I assign a single global shortcut to bring up the kitty terminal?
@@ -334,9 +315,8 @@ see :iss:`here <45>`.
I do not like the kitty icon! I do not like the kitty icon!
------------------------------- -------------------------------
The kitty icon was created as tribute to my cat of nine years who passed away, There are many alternate icons available, click on an icon to visit its
as such it is not going to change. However, if you do not like it, there are homepage:
many alternate icons available, click on an icon to visit its homepage:
.. image:: https://github.com/k0nserv/kitty-icon/raw/main/kitty.iconset/icon_256x256.png .. image:: https://github.com/k0nserv/kitty-icon/raw/main/kitty.iconset/icon_256x256.png
:target: https://github.com/k0nserv/kitty-icon :target: https://github.com/k0nserv/kitty-icon
@@ -362,10 +342,6 @@ many alternate icons available, click on an icon to visit its homepage:
:target: https://github.com/samholmes/whiskers :target: https://github.com/samholmes/whiskers
:width: 256 :width: 256
.. image:: https://github.com/user-attachments/assets/a37d7830-4a8c-45a8-988a-3e98a41ea541
:target: https://github.com/diegobit/kitty-icon
:width: 256
.. image:: https://github.com/eccentric-j/eccentric-icons/raw/main/icons/kitty-terminal/2d/kitty-preview.png .. image:: https://github.com/eccentric-j/eccentric-icons/raw/main/icons/kitty-terminal/2d/kitty-preview.png
:target: https://github.com/eccentric-j/eccentric-icons :target: https://github.com/eccentric-j/eccentric-icons
:width: 256 :width: 256
@@ -374,23 +350,9 @@ many alternate icons available, click on an icon to visit its homepage:
:target: https://github.com/eccentric-j/eccentric-icons :target: https://github.com/eccentric-j/eccentric-icons
:width: 256 :width: 256
.. image:: https://github.com/sodapopcan/kitty-icon/raw/main/kitty.app.png On macOS and X11 you can put :file:`kitty.app.icns` (macOS only) or :file:`kitty.app.png` in the
:target: https://github.com/sodapopcan/kitty-icon
:width: 256
.. image:: https://github.com/sfsam/some_icons/raw/main/kitty.app.iconset/icon_128x128@2x.png
:target: https://github.com/sfsam/some_icons
:width: 256
.. image:: https://github.com/igrmk/twiskers/raw/main/icon/twiskers.svg
:target: https://github.com/igrmk/twiskers
:width: 256
You can put :file:`kitty.app.icns` (macOS only) or :file:`kitty.app.png` in the
:ref:`kitty configuration directory <confloc>`, and this icon will be applied :ref:`kitty configuration directory <confloc>`, and this icon will be applied
automatically at startup. On X11 and Wayland, this will set the icon for kitty windows. automatically at startup. On X11, this will set the icon for kitty windows.
Note that not all Wayland compositors support the `protocol needed <https://wayland.app/protocols/xdg-toplevel-icon-v1>`__
for changing window icons.
Unfortunately, on macOS, Apple's Dock does not change its cached icon so the Unfortunately, on macOS, Apple's Dock does not change its cached icon so the
custom icon will revert when kitty is quit. Run the following to force the Dock custom icon will revert when kitty is quit. Run the following to force the Dock
@@ -429,11 +391,9 @@ This is accomplished by using ``map`` with :ac:`send_key` in :file:`kitty.conf`.
For example:: For example::
map alt+s send_key ctrl+s map alt+s send_key ctrl+s
map ctrl+alt+2 combine : send_key ctrl+c : send_key h : send_key a
This causes the program running in kitty to receive the :kbd:`ctrl+s` key when This causes the program running in kitty to receive the :kbd:`ctrl+s` key when
you press the :kbd:`alt+s` key and several keystrokes when you press you press the :kbd:`alt+s` key. To see this in action, run::
:kbd:`ctrl+alt+2`. To see this in action, run::
kitten show-key -m kitty kitten show-key -m kitty
@@ -481,8 +441,8 @@ setup environment variables system-wide, so people end up putting them in all
sorts of places where they may or may not work. sorts of places where they may or may not work.
I am using tmux/zellij and have a problem I am using tmux and have a problem
---------------------------------------------- --------------------------------------
First, terminal multiplexers are :iss:`a bad idea <391#issuecomment-638320745>`, First, terminal multiplexers are :iss:`a bad idea <391#issuecomment-638320745>`,
do not use them, if at all possible. kitty contains features that do all of what do not use them, if at all possible. kitty contains features that do all of what
@@ -504,11 +464,10 @@ for tmux refusing to support images.
If you use any of the advanced features that kitty has innovated, such as If you use any of the advanced features that kitty has innovated, such as
:doc:`styled underlines </underlines>`, :doc:`desktop notifications :doc:`styled underlines </underlines>`, :doc:`desktop notifications
</desktop-notifications>`, :doc:`variable sized text </text-sizing-protocol>`, </desktop-notifications>`, :doc:`extended keyboard support
:doc:`extended keyboard support </keyboard-protocol>`, </keyboard-protocol>`, :doc:`file transfer </kittens/transfer>`, etc.
:doc:`file transfer </kittens/transfer>`, :doc:`the ssh kitten </kittens/ssh>`, they may or may not work, depending on the whims of tmux's maintainer,
:doc:`shell integration </shell-integration>` etc. they may or may not work, your version of tmux, etc.
depending on the whims of tmux's maintainer, your version of tmux, etc.
I opened and closed a lot of windows/tabs and top shows kitty's memory usage is very high? I opened and closed a lot of windows/tabs and top shows kitty's memory usage is very high?

View File

@@ -126,7 +126,7 @@ receiving::
... ...
The client must then wait for responses from the terminal emulator. It The client must then wait for responses from the terminal emulator. It
is an error to send anymore commands to the terminal until an ``OK`` is an error to send anymore commands to to the terminal until an ``OK``
response is received from the terminal. The terminal wait for the user to accept response is received from the terminal. The terminal wait for the user to accept
the request. If accepted, it sends:: the request. If accepted, it sends::
@@ -245,7 +245,7 @@ File paths
path must be no longer than 255 UTF-8 bytes. Total path length must be no path must be no longer than 255 UTF-8 bytes. Total path length must be no
more than 4096 bytes. Paths from Windows systems must use the forward slash more than 4096 bytes. Paths from Windows systems must use the forward slash
as the separator, the first path component must be the drive letter with a as the separator, the first path component must be the drive letter with a
colon. For example: :file:`C:\\some\\file.txt` is represented as colon. For example: :file:`C:\some\file.txt` is represented as
:file:`/C:/some/file.txt`. For maximum portability, the following :file:`/C:/some/file.txt`. For maximum portability, the following
characters *should* be omitted from paths (however implementations are free characters *should* be omitted from paths (however implementations are free
to try to support them returning errors for non-representable paths):: to try to support them returning errors for non-representable paths)::

View File

@@ -50,18 +50,6 @@ Glossary
inside kitty windows and provide it with lots of powerful and flexible inside kitty windows and provide it with lots of powerful and flexible
features such as viewing images, connecting conveniently to remote features such as viewing images, connecting conveniently to remote
computers, transferring files, inputting unicode characters, etc. computers, transferring files, inputting unicode characters, etc.
They can also be written by users in Python and used to customize and
extend kitty functionality, see :doc:`kittens_intro` for details.
easing function
A function that controls how an animation progresses over time. kitty
support the `CSS syntax for easing functions
<https://developer.mozilla.org/en-US/docs/Web/CSS/easing-function>`__.
Commonly used easing functions are :code:`linear` for a constant rate
animation and :code:`ease-in-out` for an animation that starts slow,
becomes fast in the middle and ends slowly. These are used to control
various animations in kitty, such as :opt:`cursor_blink_interval` and
:opt:`visual_bell_duration`.
.. _env_vars: .. _env_vars:
@@ -98,11 +86,6 @@ Variables that influence kitty behavior
Same as :envvar:`VISUAL`. Used if :envvar:`VISUAL` is not set. Same as :envvar:`VISUAL`. Used if :envvar:`VISUAL` is not set.
.. envvar:: SHELL
Specifies the default shell kitty will run when :opt:`shell` is set to
:code:`.`.
.. envvar:: GLFW_IM_MODULE .. envvar:: GLFW_IM_MODULE
Set this to ``ibus`` to enable support for IME under X11. Set this to ``ibus`` to enable support for IME under X11.

View File

@@ -25,45 +25,36 @@ alpha-blending and text over graphics.
:alt: Demo of graphics rendering in kitty :alt: Demo of graphics rendering in kitty
:align: center :align: center
Some applications that use the kitty graphics protocol: Some programs and libraries that use the kitty graphics protocol:
* `awrit <https://github.com/chase/awrit>`_ - Chromium-based web browser rendered in Kitty with mouse and keyboard support
* `broot <https://dystroy.org/broot/>`_ - a terminal file explorer and manager, with preview of images, SVG, PDF, etc.
* `chafa <https://github.com/hpjansson/chafa>`_ - a terminal image viewer
* :doc:`kitty-diff <kittens/diff>` - a side-by-side terminal diff program with support for images
* `fzf <https://github.com/junegunn/fzf/commit/d8188fce7b7bea982e7f9050c35e488e49fb8fd0>`_ - A command line fuzzy finder
* `mpv <https://github.com/mpv-player/mpv/commit/874e28f4a41a916bb567a882063dd2589e9234e1>`_ - A video player that can play videos in the terminal
* `neofetch <https://github.com/dylanaraps/neofetch>`_ - A command line system information tool
* `pixcat <https://github.com/mirukana/pixcat>`_ - a third party CLI and python library that wraps the graphics protocol
* `ranger <https://github.com/ranger/ranger>`_ - a terminal file manager, with image previews
* `termpdf.py <https://github.com/dsanson/termpdf.py>`_ - a terminal PDF/DJVU/CBR viewer * `termpdf.py <https://github.com/dsanson/termpdf.py>`_ - a terminal PDF/DJVU/CBR viewer
* `timg <https://github.com/hzeller/timg>`_ - a terminal image and video viewer * `ranger <https://github.com/ranger/ranger>`_ - a terminal file manager, with image previews
* :doc:`kitty-diff <kittens/diff>` - a side-by-side terminal diff program with support for images
* `tpix <https://github.com/jesvedberg/tpix>`_ - a statically compiled binary that can be used to display images and easily installed on remote servers without root access * `tpix <https://github.com/jesvedberg/tpix>`_ - a statically compiled binary that can be used to display images and easily installed on remote servers without root access
* `twitch-tui <https://github.com/Xithrius/twitch-tui>`_ - Twitch chat in the terminal * `mpv <https://github.com/mpv-player/mpv/commit/874e28f4a41a916bb567a882063dd2589e9234e1>`_ - A video player that can play videos in the terminal
* `vat <https://github.com/jzbrooks/vat>`_ - a terminal image viewer for vector graphics, including Android Vector Drawables * `pixcat <https://github.com/mirukana/pixcat>`_ - a third party CLI and python library that wraps the graphics protocol
* `neofetch <https://github.com/dylanaraps/neofetch>`_ - A command line system
information tool
* `viu <https://github.com/atanunq/viu>`_ - a terminal image viewer * `viu <https://github.com/atanunq/viu>`_ - a terminal image viewer
* `Yazi <https://github.com/sxyazi/yazi>`_ - Blazing fast terminal file manager written in Rust, based on async I/O
Libraries:
* `ctx.graphics <https://ctx.graphics/>`_ - Library for drawing graphics * `ctx.graphics <https://ctx.graphics/>`_ - Library for drawing graphics
* `timg <https://github.com/hzeller/timg>`_ - a terminal image and video viewer
* `notcurses <https://github.com/dankamongmen/notcurses>`_ - C library for terminal graphics with bindings for C++, Rust and Python * `notcurses <https://github.com/dankamongmen/notcurses>`_ - C library for terminal graphics with bindings for C++, Rust and Python
* `rasterm <https://github.com/BourgeoisBear/rasterm>`_ - Go library to display images in the terminal * `rasterm <https://github.com/BourgeoisBear/rasterm>`_ - Go library to display images in the terminal
* `chafa <https://github.com/hpjansson/chafa>`_ - a terminal image viewer
* `hologram.nvim <https://github.com/edluffy/hologram.nvim>`_ - view images inside nvim * `hologram.nvim <https://github.com/edluffy/hologram.nvim>`_ - view images inside nvim
* `image.nvim <https://github.com/3rd/image.nvim>`_ - Bringing images to neovim
* `image_preview.nvim <https://github.com/adelarsq/image_preview.nvim/>`_ - Image preview for neovim
* `kui.nvim <https://github.com/romgrk/kui.nvim>`_ - Build sophisticated UIs inside neovim using the kitty graphics protocol * `kui.nvim <https://github.com/romgrk/kui.nvim>`_ - Build sophisticated UIs inside neovim using the kitty graphics protocol
* `image.nvim <https://github.com/3rd/image.nvim>`_ - Bringing images to neovim
* `term-image <https://github.com/AnonymouX47/term-image>`_ - A Python library, CLI and TUI to display and browse images in the terminal * `term-image <https://github.com/AnonymouX47/term-image>`_ - A Python library, CLI and TUI to display and browse images in the terminal
* `glkitty <https://github.com/michaeljclark/glkitty>`_ - C library to draw OpenGL shaders in the terminal with a glgears demo * `glkitty <https://github.com/michaeljclark/glkitty>`_ - C library to draw OpenGL shaders in the terminal with a glgears demo
* `twitch-tui <https://github.com/Xithrius/twitch-tui>`_ - Twitch chat in the terminal
* `awrit <https://github.com/chase/awrit>`_ - Chromium-based web browser rendered in Kitty with mouse and keyboard support
* `fzf <https://github.com/junegunn/fzf/commit/d8188fce7b7bea982e7f9050c35e488e49fb8fd0>`_ - A command line fuzzy finder
Other terminals that have implemented the graphics protocol: Other terminals that have implemented the graphics protocol:
* `Ghostty <https://ghostty.org>`_
* `Konsole <https://invent.kde.org/utilities/konsole/-/merge_requests/594>`_
* `st (with a patch) <https://st.suckless.org/patches/kitty-graphics-protocol>`_
* `Warp <https://docs.warp.dev/getting-started/changelog#id-2025.03.26-v0.2025.03.26.08.10>`_
* `wayst <https://github.com/91861/wayst>`_
* `WezTerm <https://github.com/wez/wezterm/issues/986>`_ * `WezTerm <https://github.com/wez/wezterm/issues/986>`_
* `Konsole <https://invent.kde.org/utilities/konsole/-/merge_requests/594>`_
* `wayst <https://github.com/91861/wayst>`_
Getting the window size Getting the window size
@@ -237,7 +228,7 @@ This is a so-called *Application Programming Command (APC)*. Most terminal
emulators ignore APC codes, making it safe to use. emulators ignore APC codes, making it safe to use.
The control data is a comma-separated list of ``key=value`` pairs. The payload The control data is a comma-separated list of ``key=value`` pairs. The payload
is arbitrary binary data, :rfc:`base64 <4648>` encoded to prevent interoperation problems is arbitrary binary data, base64-encoded to prevent interoperation problems
with legacy terminals that get confused by control codes within an APC code. with legacy terminals that get confused by control codes within an APC code.
The meaning of the payload is interpreted based on the control data. The meaning of the payload is interpreted based on the control data.
@@ -298,8 +289,7 @@ compression is supported, which is specified using ``o=z``. For example::
<ESC>_Gf=24,s=10,v=20,o=z;<payload><ESC>\ <ESC>_Gf=24,s=10,v=20,o=z;<payload><ESC>\
This is the same as the example from the RGB data section, except that the This is the same as the example from the RGB data section, except that the
payload is now compressed using deflate (this occurs prior to payload is now compressed using deflate (this occurs prior to base64-encoding).
:rfc:`base64 <4648>` encoding).
The terminal emulator will decompress it before rendering. You can specify The terminal emulator will decompress it before rendering. You can specify
compression for any format. The terminal emulator will decompress before compression for any format. The terminal emulator will decompress before
interpreting the pixel data. interpreting the pixel data.
@@ -371,7 +361,7 @@ Remote clients, those that are unable to use the filesystem/shared memory to
transmit data, must send the pixel data directly using escape codes. Since transmit data, must send the pixel data directly using escape codes. Since
escape codes are of limited maximum length, the data will need to be chunked up escape codes are of limited maximum length, the data will need to be chunked up
for transfer. This is done using the ``m`` key. The pixel data must first be for transfer. This is done using the ``m`` key. The pixel data must first be
:rfc:`base64 <4648>` encoded then chunked up into chunks no larger than ``4096`` bytes. All base64 encoded then chunked up into chunks no larger than ``4096`` bytes. All
chunks, except the last, must have a size that is a multiple of 4. The client chunks, except the last, must have a size that is a multiple of 4. The client
then sends the graphics escape code as usual, with the addition of an ``m`` key then sends the graphics escape code as usual, with the addition of an ``m`` key
that must have the value ``1`` for all but the last chunk, where it must be that must have the value ``1`` for all but the last chunk, where it must be
@@ -419,7 +409,9 @@ use the *query action*, set ``a=q``. Then the terminal emulator will try to load
the image and respond with either OK or an error, as above, but it will not the image and respond with either OK or an error, as above, but it will not
replace an existing image with the same id, nor will it store the image. replace an existing image with the same id, nor will it store the image.
We intend that any terminal emulator that wishes to support it can do so. To As of May 2023, kitty has a complete implementation of this protocol and
WezTerm has a mostly complete implementation. Konsole and wayst have partial
support. We intend that any terminal emulator that wishes to support it can do so. To
check if a terminal emulator supports the graphics protocol the best way is to check if a terminal emulator supports the graphics protocol the best way is to
send the above *query action* followed by a request for the `primary device send the above *query action* followed by a request for the `primary device
attributes <https://vt100.net/docs/vt510-rm/DA1.html>`_. If you get back an attributes <https://vt100.net/docs/vt510-rm/DA1.html>`_. If you get back an
@@ -756,9 +748,6 @@ deleted, if the capital letter form above is specified. Also, when the terminal
is running out of quota space for new images, existing images without is running out of quota space for new images, existing images without
placements will be preferentially deleted. placements will be preferentially deleted.
If an image is being loaded in chunks and the upload is not complete when any
delete command is received, the partial upload must be aborted.
Some examples:: Some examples::
<ESC>_Ga=d<ESC>\ # delete all visible placements <ESC>_Ga=d<ESC>\ # delete all visible placements
@@ -962,7 +951,7 @@ by the ``C`` key with the default being to alpha blend the source rectangle
onto the destination rectangle. With ``C=1`` it will be a simple replacement onto the destination rectangle. With ``C=1`` it will be a simple replacement
of pixels. For example:: of pixels. For example::
<ESC>_Ga=c,i=1,r=7,c=9,w=23,h=27,X=4,Y=8,x=1,y=3<ESC>\ <ESC>_Gi=1,r=7,c=9,w=23,h=27,X=4,Y=8,x=1,y=3<ESC>\
Will compose a ``23x27`` rectangle located at ``(4, 8)`` in the ``7th frame`` Will compose a ``23x27`` rectangle located at ``(4, 8)`` in the ``7th frame``
onto the rectangle located at ``(1, 3)`` in the ``9th frame``. These will be onto the rectangle located at ``(1, 3)`` in the ``9th frame``. These will be

View File

@@ -19,14 +19,14 @@ kitty
.. tab:: Fast .. tab:: Fast
* Uses GPU and SIMD vector CPU instructions for :doc:`best in class <performance>` * Offloads rendering to the GPU for :doc:`lower system load <performance>`
* Uses threaded rendering for :iss:`absolutely minimal latency <2701#issuecomment-636497270>` * Uses threaded rendering for :iss:`absolutely minimal latency <2701#issuecomment-636497270>`
* Performance tradeoffs can be :ref:`tuned <conf-kitty-performance>` * Performance tradeoffs can be :ref:`tuned <conf-kitty-performance>`
.. tab:: Capable .. tab:: Capable
* Graphics, with :doc:`images and animations <graphics-protocol>` * Graphics, with :doc:`images and animations <graphics-protocol>`
* Ligatures, emoji with :opt:`per glyph font substitution <symbol_map>` and :doc:`variable fonts and font features </kittens/choose-fonts>` * Ligatures and emoji, with :opt:`per glyph font substitution <symbol_map>`
* :term:`Hyperlinks<hyperlinks>`, with :doc:`configurable actions <open_actions>` * :term:`Hyperlinks<hyperlinks>`, with :doc:`configurable actions <open_actions>`
.. tab:: Scriptable .. tab:: Scriptable

View File

@@ -49,7 +49,9 @@ detect_os() {
amd64|x86_64) arch="x86_64";; amd64|x86_64) arch="x86_64";;
aarch64*) arch="arm64";; aarch64*) arch="arm64";;
armv8*) arch="arm64";; armv8*) arch="arm64";;
*) die "kitty binaries not available for architecture $(command uname -m)";; i386) arch="i686";;
i686) arch="i686";;
*) die "Unknown CPU architecture $(command uname -m)";;
esac esac
;; ;;
*) die "kitty binaries are not available for $(command uname)" *) die "kitty binaries are not available for $(command uname)"
@@ -98,9 +100,6 @@ get_release_url() {
get_file_url "v$release_version" "$release_version" get_file_url "v$release_version" "$release_version"
} }
get_version_url() {
get_file_url "v$1" "$1"
}
get_nightly_url() { get_nightly_url() {
get_file_url "nightly" "nightly" get_file_url "nightly" "nightly"
@@ -111,7 +110,6 @@ get_download_url() {
case "$installer" in case "$installer" in
"nightly") get_nightly_url ;; "nightly") get_nightly_url ;;
"") get_release_url ;; "") get_release_url ;;
version-*) get_version_url "${installer#*-}";;
*) installer_is_file="y" ;; *) installer_is_file="y" ;;
esac esac
} }
@@ -130,24 +128,20 @@ download_installer() {
} }
} }
ensure_dest() {
printf "%s\n" "Installing to $dest"
command rm -rf "$dest" || die "Failed to delete $dest"
command mkdir -p "$dest" || die "Failed to mkdir -p $dest"
command rm -rf "$dest" || die "Failed to delete $dest"
}
linux_install() { linux_install() {
command mkdir "$tdir/mp" command mkdir "$tdir/mp"
command tar -C "$tdir/mp" "-xJof" "$installer" || die "Failed to extract kitty tarball" command tar -C "$tdir/mp" "-xJof" "$installer" || die "Failed to extract kitty tarball"
ensure_dest printf "%s\n" "Installing to $dest"
command rm -rf "$dest" || die "Failed to delete $dest"
command mv "$tdir/mp" "$dest" || die "Failed to move kitty.app to $dest" command mv "$tdir/mp" "$dest" || die "Failed to move kitty.app to $dest"
} }
macos_install() { macos_install() {
command mkdir "$tdir/mp" command mkdir "$tdir/mp"
command hdiutil attach "$installer" "-mountpoint" "$tdir/mp" || die "Failed to mount kitty.dmg" command hdiutil attach "$installer" "-mountpoint" "$tdir/mp" || die "Failed to mount kitty.dmg"
ensure_dest printf "%s\n" "Installing to $dest"
command rm -rf "$dest"
command mkdir -p "$dest" || die "Failed to create the directory: $dest"
command ditto -v "$tdir/mp/kitty.app" "$dest" command ditto -v "$tdir/mp/kitty.app" "$dest"
rc="$?" rc="$?"
command hdiutil detach "$tdir/mp" command hdiutil detach "$tdir/mp"

View File

@@ -20,24 +20,6 @@ images and other types of documents directly in your terminal, even over SSH.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A terminal PDF/DJVU/CBR viewer A terminal PDF/DJVU/CBR viewer
.. _tool_tdf:
`tdf <https://github.com/itsjunetime/tdf>`_
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A terminal PDF viewer
.. _tool_fancy_cat:
`fancy-cat <https://github.com/freref/fancy-cat>`_
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A terminal PDF viewer
.. _tool_meowpdf:
`meowpdf <https://github.com/monoamine11231/meowpdf>`_
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A terminal PDF viewer with GUI-like usage and Vim-like keybindings written in Rust
.. _tool_mdcat: .. _tool_mdcat:
`mdcat <https://github.com/lunaryorn/mdcat>`_ `mdcat <https://github.com/lunaryorn/mdcat>`_
@@ -58,20 +40,6 @@ graphics protocol.
Another terminal file manager, with previews of file contents powered by kitty's Another terminal file manager, with previews of file contents powered by kitty's
graphics protocol. graphics protocol.
.. _tool_yazi:
`Yazi <https://github.com/sxyazi/yazi>`_
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Blazing fast terminal file manager, with built-in kitty graphics protocol support
(implemented both Classic protocol and Unicode placeholders).
.. _tool_clifm:
`clifm <https://github.com/leo-arch/clifm>`__
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The shell-like, command line terminal file manager, uses the kitty graphics and
keyboard protocols.
.. _tool_hunter: .. _tool_hunter:
`hunter <https://github.com/rabite0/hunter>`_ `hunter <https://github.com/rabite0/hunter>`_
@@ -119,20 +87,11 @@ base application that uses kitty's graphics protocol for images.
A text mode WWW browser that supports kitty's graphics protocol to display A text mode WWW browser that supports kitty's graphics protocol to display
images. images.
.. _tool_awrit:
`awrit <https://github.com/chase/awrit>`__ `awrit <https://github.com/chase/awrit>`__
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A full Chromium based web browser running in the terminal using kitty's A full Chromium based web browser running in the terminal using kitty's
graphics protocol. graphics protocol.
.. _tool_chawan:
`chawan <https://sr.ht/~bptato/chawan/>`__
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A text mode WWW browser that supports kitty's graphics protocol to display
images.
.. _tool_mpv: .. _tool_mpv:
`mpv <https://github.com/mpv-player/mpv/commit/874e28f4a41a916bb567a882063dd2589e9234e1>`_ `mpv <https://github.com/mpv-player/mpv/commit/874e28f4a41a916bb567a882063dd2589e9234e1>`_
@@ -160,13 +119,9 @@ protocol
.. _tool_matplotlib: .. _tool_matplotlib:
matplotlib `matplotlib <https://github.com/jktr/matplotlib-backend-kitty>`_
^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Show matplotlib plots directly in kitty
There exist multiple backends for matplotlib to draw images directly in kitty.
* `matplotlib-backend-kitty <https://github.com/jktr/matplotlib-backend-kitty>`__
* `kitcat <https://github.com/mil-ad/kitcat>`__
.. _tool_KittyTerminalImage: .. _tool_KittyTerminalImage:
@@ -240,13 +195,6 @@ A tool to display weather information in your terminal with curl
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
View and manage the system clipboard under Wayland in your kitty terminal View and manage the system clipboard under Wayland in your kitty terminal
.. tool_nemu:
`NEMU <https://github.com/nemuTUI/nemu>`__
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TUI for QEMU used to manage virtual machines, can display the Virtual Machine
in the terminal using the kitty graphics protocol.
Editor integration Editor integration
----------------------- -----------------------
@@ -279,14 +227,13 @@ consistent set of hotkeys.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Allows easily running tests in a terminal window Allows easily running tests in a terminal window
.. tool_nvim_image_viewers: .. tool_hologram:
`hologram.nvim <https://github.com/edluffy/hologram.nvim>`_
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Terminal image viewer for Neovim. For a bit of fun, you can even have `cats
running around inside nvim <https://github.com/giusgad/pets.nvim>`__.
Various image viewing plugins for editors
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* `snacks.nvim <https://github.com/folke/snacks.nvim>`__ - Enables seamless inline images in various file formats within nvim
* `image.nvim <https://github.com/3rd/image.nvim>`_ - Bringing images to neovim
* `image_preview.nvim <https://github.com/adelarsq/image_preview.nvim/>`_ - Image preview for neovim
* `hologram.nvim <https://github.com/edluffy/hologram.nvim>`_ - view images inside nvim
Scrollback manipulation Scrollback manipulation
------------------------- -------------------------
@@ -310,29 +257,10 @@ Live incremental search of the scrollback buffer.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Keyboard based text selection for the kitty scrollback buffer. Keyboard based text selection for the kitty scrollback buffer.
Desktop panels
-------------------------
`kitty panel <https://github.com/5hubham5ingh/kitty-panel>`__
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A system panel for Kitty terminal that displays real-time system metrics using terminal-based utilities.
`pawbar <https://github.com/codelif/pawbar>`__
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A kitten-panel based desktop panel for your desktop
Miscellaneous Miscellaneous
------------------ ------------------
.. tool_gattino:
`gattino <https://github.com/salvozappa/gattino>`__
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Integrate kitty with an LLM to convert plain language prompts into shell
commands.
.. tool_kitty_smart_tab: .. tool_kitty_smart_tab:
`kitty-smart-tab <https://github.com/yurikhan/kitty-smart-tab>`_ `kitty-smart-tab <https://github.com/yurikhan/kitty-smart-tab>`_

View File

@@ -28,28 +28,20 @@ issues in that proposal, listed at the :ref:`bottom of this document
You can see this protocol with all enhancements in action by running:: You can see this protocol with all enhancements in action by running::
kitten show-key -m kitty kitten show_key -m kitty
inside the kitty terminal to report key events. inside the kitty terminal to report key events.
In addition to kitty, this protocol is also implemented in: In addition to kitty, this protocol is also implemented in:
* The `alacritty terminal <https://github.com/alacritty/alacritty/pull/7125>`__
* The `ghostty terminal <https://ghostty.org>`__
* The `foot terminal <https://codeberg.org/dnkl/foot/issues/319>`__ * The `foot terminal <https://codeberg.org/dnkl/foot/issues/319>`__
* The `iTerm2 terminal <https://gitlab.com/gnachman/iterm2/-/issues/10017>`__
* The `rio terminal <https://github.com/raphamorim/rio/commit/cd463ca37677a0fc48daa8795ea46dadc92b1e95>`__
* The `WezTerm terminal <https://wezfurlong.org/wezterm/config/lua/config/enable_kitty_keyboard.html>`__ * The `WezTerm terminal <https://wezfurlong.org/wezterm/config/lua/config/enable_kitty_keyboard.html>`__
* The `alacritty terminal <https://github.com/alacritty/alacritty/pull/7125>`__
Libraries implementing this protocol: * The `rio terminal <https://github.com/raphamorim/rio/commit/cd463ca37677a0fc48daa8795ea46dadc92b1e95>`__
* The `notcurses library
* The `notcurses library <https://github.com/dankamongmen/notcurses/issues/2131>`__ <https://github.com/dankamongmen/notcurses/issues/2131>`__
* The `crossterm library <https://github.com/crossterm-rs/crossterm/pull/688>`__ * The `crossterm library
* The `textual library <https://github.com/Textualize/textual/pull/4631>`__ <https://github.com/crossterm-rs/crossterm/pull/688>`__
* The vaxis library `go <https://sr.ht/~rockorager/vaxis/>`__ and `zig <https://github.com/rockorager/libvaxis/>`__
Programs implementing this protocol:
* The `Vim text editor <https://github.com/vim/vim/commit/63a2e360cca2c70ab0a85d14771d3259d4b3aafa>`__ * The `Vim text editor <https://github.com/vim/vim/commit/63a2e360cca2c70ab0a85d14771d3259d4b3aafa>`__
* The `Emacs text editor via the kkp package <https://github.com/benjaminor/kkp>`__ * The `Emacs text editor via the kkp package <https://github.com/benjaminor/kkp>`__
* The `Neovim text editor <https://github.com/neovim/neovim/pull/18181>`__ * The `Neovim text editor <https://github.com/neovim/neovim/pull/18181>`__
@@ -57,15 +49,9 @@ Programs implementing this protocol:
* The `dte text editor <https://gitlab.com/craigbarnes/dte/-/issues/138>`__ * The `dte text editor <https://gitlab.com/craigbarnes/dte/-/issues/138>`__
* The `Helix text editor <https://github.com/helix-editor/helix/pull/4939>`__ * The `Helix text editor <https://github.com/helix-editor/helix/pull/4939>`__
* The `far2l file manager <https://github.com/elfmz/far2l/commit/e1f2ee0ef2b8332e5fa3ad7f2e4afefe7c96fc3b>`__ * The `far2l file manager <https://github.com/elfmz/far2l/commit/e1f2ee0ef2b8332e5fa3ad7f2e4afefe7c96fc3b>`__
* The `Yazi file manager <https://github.com/sxyazi/yazi>`__ * The `yazi file manager <https://github.com/sxyazi/yazi>`__
* The `awrit web browser <https://github.com/chase/awrit>`__ * The `awrit web browser <https://github.com/chase/awrit>`__
* The `Turbo Vision <https://github.com/magiblot/tvision/commit/6e5a7b46c6634079feb2ac98f0b890bbed59f1ba>`__/`Free Vision <https://gitlab.com/freepascal.org/fpc/source/-/issues/40673#note_2061428120>`__ IDEs
* The `aerc email client <https://git.sr.ht/~rjarry/aerc/commit/d73cf33c2c6c3e564ce8aff04acc329a06eafc54>`__
Shells implementing this protocol:
* The `nushell shell <https://github.com/nushell/nushell/pull/10540>`__ * The `nushell shell <https://github.com/nushell/nushell/pull/10540>`__
* The `fish shell <https://github.com/fish-shell/fish-shell/commit/8bf8b10f685d964101f491b9cc3da04117a308b4>`__
.. versionadded:: 0.20.0 .. versionadded:: 0.20.0
@@ -164,7 +150,7 @@ keyboard with a Cyrillic keyboard layout pressing the :kbd:`ctrl+С` key will
be :kbd:`ctrl+c` in the standard layout. So the terminal should send the *base be :kbd:`ctrl+c` in the standard layout. So the terminal should send the *base
layout key* as ``99`` corresponding to the ``c`` key. layout key* as ``99`` corresponding to the ``c`` key.
If only one alternate key is present, it is the *shifted key*. If the terminal If only one alternate key is present, it is the *shifted key* if the terminal
wants to send only a base layout key but no shifted key, it must use an empty wants to send only a base layout key but no shifted key, it must use an empty
sub-field for the shifted key, like this:: sub-field for the shifted key, like this::
@@ -337,7 +323,7 @@ the bytes used for CSI control codes.
Turning on this flag will cause the terminal to report the :kbd:`Esc`, :kbd:`alt+key`, Turning on this flag will cause the terminal to report the :kbd:`Esc`, :kbd:`alt+key`,
:kbd:`ctrl+key`, :kbd:`ctrl+alt+key`, :kbd:`shift+alt+key` keys using ``CSI u`` sequences instead :kbd:`ctrl+key`, :kbd:`ctrl+alt+key`, :kbd:`shift+alt+key` keys using ``CSI u`` sequences instead
of legacy ones. Here key is any ASCII key as described in :ref:`legacy_text`. of legacy ones. Here key is any ASCII key as described in :ref:`legacy_text`.
Additionally, all non text keypad keys will be reported as separate keys with ``CSI u`` Additionally, all keypad keys will be reported as separate keys with ``CSI u``
encoding, using dedicated numbers from the :ref:`table below <functional>`. encoding, using dedicated numbers from the :ref:`table below <functional>`.
With this flag turned on, all key events that do not generate text are With this flag turned on, all key events that do not generate text are
@@ -380,13 +366,8 @@ Report alternate keys
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This progressive enhancement (``0b100``) causes the terminal to report This progressive enhancement (``0b100``) causes the terminal to report
alternate key values *in addition* to the main value, to aid in shortcut alternate key values in addition to the main value, to aid in shortcut
matching. See :ref:`key_codes` for details on how these are reported. Note that matching. See :ref:`key_codes` for details on how these are reported.
this flag is a pure enhancement to the form of the escape code used to
represent key events, only key events represented as escape codes due to the
other enhancements in effect will be affected by this enhancement. In other
words, only if a key event was already going to be represented as an escape
code due to one of the other enhancements will this enhancement affect it.
.. _report_all_keys: .. _report_all_keys:
@@ -405,20 +386,17 @@ Report associated text enhancement below.
Additionally, with this mode, events for pressing modifier keys are reported. Additionally, with this mode, events for pressing modifier keys are reported.
Note that *all* keys are reported as escape codes, including :kbd:`Enter`, Note that *all* keys are reported as escape codes, including :kbd:`Enter`,
:kbd:`Tab`, :kbd:`Backspace` etc. Note that this enhancement implies all keys :kbd:`Tab`, :kbd:`Backspace` etc.
are automatically disambiguated as well, since they are represented in their
canonical escape code form.
.. _report_text: .. _report_text:
Report associated text Report associated text
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This progressive enhancement (``0b10000``) *additionally* causes key events that This progressive enhancement (``0b10000``) causes key events that generate text
generate text to be reported as ``CSI u`` escape codes with the text embedded to be reported as ``CSI u`` escape codes with the text embedded in the escape
in the escape code. See :ref:`text_as_codepoints` above for details on the code. See :ref:`text_as_codepoints` above for details on the mechanism.
mechanism. Note that this flag is an enhancement to :ref:`report_all_keys`
and is undefined if used without it.
.. _detection: .. _detection:
@@ -433,13 +411,6 @@ followed by request for the `primary device attributes
attributes is received without getting back an answer for the progressive attributes is received without getting back an answer for the progressive
enhancement the terminal does not support this protocol. enhancement the terminal does not support this protocol.
.. note::
Terminal implementations of this protocol are **strongly** encouraged to
implement all progressive enhancements. It does not make sense to
implement only a subset. Nonetheless, there are likely to be some terminal
implementations that do not do so, applications can detect such
implementations by first setting the desired progressive enhancements and
then querying for the :ref:`current progressive enhancement <progressive_enhancement>`
Legacy key event encoding Legacy key event encoding
-------------------------------- --------------------------------

View File

@@ -1,128 +0,0 @@
Changing kitty fonts
========================
.. only:: man
Overview
--------------
Terminal aficionados spend all day staring at text, as such, getting text
rendering just right is very important. kitty has extremely powerful facilities
for fine-tuning text rendering. It supports `OpenType features
<https://en.wikipedia.org/wiki/List_of_typographic_features>`__ to select
alternate glyph shapes, and `Variable fonts
<https://en.wikipedia.org/wiki/Variable_font>`__ to control the weight or
spacing of a font precisely. You can also :opt:`select which font is used to
render particular unicode codepoints <symbol_map>` and you can :opt:`modify
font metrics <modify_font>` and even :opt:`adjust the gamma curves
<text_composition_strategy>` used for rendering text onto the background color.
The first step is to select the font faces kitty will use for rendering
regular, bold and italic text. kitty comes with a convenient UI for choosing fonts,
in the form of the *choose-fonts* kitten. Simply run::
kitten choose-fonts
and follow the on screen prompts.
First, choose the family you want, the list of families can be easily filtered by
typing a few letters from the family name you are looking for. The family
selection screen shows you a preview of how the family looks.
.. image:: ../screenshots/family-selection.png
:alt: Choosing a family with the choose fonts kitten
:width: 600
Once you select a family by pressing the :kbd:`Enter` key, you
are shown previews of what the regular, bold and italic faces look like
for that family. You can choose to fine tune any of the faces. Start with
fine-tuning the regular face by pressing the :kbd:`R` key. The other styles
will be automatically adjusted based on what you select for the regular face.
.. image:: ../screenshots/font-fine-tune.png
:alt: Fine tune a font by choosing a precise weight and features
:width: 600
You can choose a specific style or font feature by clicking on it. A precise
value for any variable axes can be selected using the slider, in the screenshot
above, the font supports precise weight adjustment. If you are lucky the font
designer has included descriptive names for font features, which will be
displayed, if not, consult the documentation of the font to see what each feature does.
.. _font_spec_syntax:
The font specification syntax
--------------------------------
If you don't like the choose fonts kitten or simply want to understand and
write font selection options into :file:`kitty.conf` yourself, read on.
There are four font face selection keys: `font_family`, `bold_font`,
`italic_font` and `bold_italic_font`. Each of these supports the syntax
described below. Their values can be of three types, either a
font family name, the keyword ``auto`` or an extended ``key=value`` syntax
for specifying font selection precisely.
If a font family name is specified kitty will use Operating System APIs to
search for a matching font. The keyword ``auto`` means kitty will choose a font
completely automatically, typically this is used for automatically selecting
bold/italic variants once the :opt:`font_family` is set. The bold and italic
variants will then automatically use the same set of features as the main face.
To specify font face selection more precisely, a ``key=value`` syntax is used.
First, let's look at a few examples::
# Select by family only, actual face selection is automatic
font_family family="Fira Code"
# Select an exact face by Postscript name
font_family postscript_name=FiraCode
# Select an exact face by family with features and variable weight
font_family family=SourceCodeVF variable_name=SourceCodeUpright features="+zero cv01=2" wght=380
The following are the known keys, any other keys are names of *variable axes*,
that is, they are used to set the variable value for some font characteristic.
``family``
A font family name. A family typically has multiple actual font faces, such
as bold and italic variants. One or more of the faces can even be variable,
allowing fine tuning of font characteristics.
``style``
A style name to choose a particular font from a given family. Useful only
with the ``family`` key, when no more precise methods for face selection
are specified. Can also be used to specify a named variable style for
variable fonts.
``postscript_name``
The actual postscript name for a font face. This allows selecting a
particular variant within a font family. But note that postscript names
are usually insufficient for selecting variable fonts.
``full_name``
This can be used to select a particular font face in a family. However, it
is less precise than ``postscript_name`` and should not generally be used.
``variable_name``
Some families with variable fonts actually contain multiple font files. For
example, a family could have variable weights with one font file containing
upright variable weight faces and another containing italic variable weight
faces. Well designed fonts use a *variable name* to distinguish between
such files. Should be used in conjunction with ``family`` to select a
particular variable font file.
``features``
A space separated list of OpenType font features to enable/disable or
select a value of, for this font. Consult the documentation for the font
family to see what features it supports and their effects. The exact syntax
for specifying features is `documented by HarfBuzz
<https://harfbuzz.github.io/harfbuzz-hb-common.html#hb-feature-from-string>`__
``system``
This can be used to pass an arbitrary string, usuall a family or full name
to the OS font selection APIs. Should not be used in conjunction with any
other keys. Is the same as specifying just the font name without any keys.
In addition to these keys, any four letter key is treated as the name of a
variable characteristic of the font. It's value is used to set the value for
the name.

View File

@@ -19,9 +19,10 @@ Create a file in the kitty config directory, :file:`~/.config/kitty/mykitten.py`
.. code-block:: python .. code-block:: python
from typing import List
from kitty.boss import Boss from kitty.boss import Boss
def main(args: list[str]) -> str: def main(args: List[str]) -> str:
# this is the main entry point of the kitten, it will be executed in # this is the main entry point of the kitten, it will be executed in
# the overlay window when the kitten is launched # the overlay window when the kitten is launched
answer = input('Enter some text: ') answer = input('Enter some text: ')
@@ -29,7 +30,7 @@ Create a file in the kitty config directory, :file:`~/.config/kitty/mykitten.py`
# handle_result() function # handle_result() function
return answer return answer
def handle_result(args: list[str], answer: str, target_window_id: int, boss: Boss) -> None: def handle_result(args: List[str], answer: str, target_window_id: int, boss: Boss) -> None:
# get the kitty window into which to paste answer # get the kitty window into which to paste answer
w = boss.window_id_map.get(target_window_id) w = boss.window_id_map.get(target_window_id)
if w is not None: if w is not None:
@@ -59,7 +60,7 @@ would pass to ``kitten @``. For example:
.. code-block:: python .. code-block:: python
def handle_result(args: list[str], answer: str, target_window_id: int, boss: Boss) -> None: def handle_result(args: List[str], answer: str, target_window_id: int, boss: Boss) -> None:
# get the kitty window to which to send text # get the kitty window to which to send text
w = boss.window_id_map.get(target_window_id) w = boss.window_id_map.get(target_window_id)
if w is not None: if w is not None:
@@ -72,9 +73,6 @@ would pass to ``kitten @``. For example:
shown above or ``--self``. shown above or ``--self``.
Run, ``kitten @ --help`` in a kitty terminal, to see all the remote control
commands available to you.
Passing arguments to kittens Passing arguments to kittens
------------------------------ ------------------------------
@@ -100,18 +98,19 @@ like. For example:
.. code-block:: py .. code-block:: py
from typing import List
from kitty.boss import Boss from kitty.boss import Boss
# in main, STDIN is for the kitten process and will contain # in main, STDIN is for the kitten process and will contain
# the contents of the screen # the contents of the screen
def main(args: list[str]) -> str: def main(args: List[str]) -> str:
return sys.stdin.read() return sys.stdin.read()
# in handle_result, STDIN is for the kitty process itself, rather # in handle_result, STDIN is for the kitty process itself, rather
# than the kitten process and should not be read from. # than the kitten process and should not be read from.
from kittens.tui.handler import result_handler from kittens.tui.handler import result_handler
@result_handler(type_of_input='text') @result_handler(type_of_input='text')
def handle_result(args: list[str], stdin_data: str, target_window_id: int, boss: Boss) -> None: def handle_result(args: List[str], stdin_data: str, target_window_id: int, boss: Boss) -> None:
pass pass
@@ -169,14 +168,15 @@ Create a Python file in the :ref:`kitty config directory <confloc>`,
.. code-block:: py .. code-block:: py
from typing import List
from kitty.boss import Boss from kitty.boss import Boss
def main(args: list[str]) -> str: def main(args: List[str]) -> str:
pass pass
from kittens.tui.handler import result_handler from kittens.tui.handler import result_handler
@result_handler(no_ui=True) @result_handler(no_ui=True)
def handle_result(args: list[str], answer: str, target_window_id: int, boss: Boss) -> None: def handle_result(args: List[str], answer: str, target_window_id: int, boss: Boss) -> None:
tab = boss.active_tab tab = boss.active_tab
if tab is not None: if tab is not None:
if tab.current_layout.name == 'stack': if tab.current_layout.name == 'stack':
@@ -228,56 +228,6 @@ The function will only send the event if the program is receiving events of
that type, and will return ``True`` if it sent the event, and ``False`` if not. that type, and will return ``True`` if it sent the event, and ``False`` if not.
.. _kitten_main_rc:
Using remote control inside the main() kitten function
------------------------------------------------------------
You can use kitty's remote control features inside the main() function of a
kitten, even without enabling remote control. This is useful if you want to
probe kitty for more information before presenting some UI to the user or if
you want the user to be able to control kitty from within your kitten's UI
rather than after it has finished running. To enable it, simply tell kitty your kitten
requires remote control, as shown in the example below::
import json
import sys
from pprint import pprint
from kittens.tui.handler import kitten_ui
@kitten_ui(allow_remote_control=True)
def main(args: list[str]) -> str:
# get the result of running kitten @ ls
cp = main.remote_control(['ls'], capture_output=True)
if cp.returncode != 0:
sys.stderr.buffer.write(cp.stderr)
raise SystemExit(cp.returncode)
output = json.loads(cp.stdout)
pprint(output)
# open a new tab with a title specified by the user
title = input('Enter the name of tab: ')
window_id = main.remote_control(['launch', '--type=tab', '--tab-title', title], check=True, capture_output=True).stdout.decode()
return window_id
:code:`allow_remote_control=True` tells kitty to run this kitten with remote
control enabled, regardless of whether it is enabled globally or not.
To run a remote control command use the :code:`main.remote_control()` function
which is a thin wrapper around Python's :code:`subprocess.run` function. Note
that by default, for security, child processes launched by your kitten cannot use remote
control, thus it is necessary to use :code:`main.remote_control()`. If you wish
to enable child processes to use remote control, call
:code:`main.allow_indiscriminate_remote_control()`.
Remote control access can be further secured by using
:code:`kitten_ui(allow_remote_control=True, remote_control_password='ls set-colors')`.
This will use a secure generated password to restrict remote control.
You can specify a space separated list of remote control commands to allow, see
:opt:`remote_control_password` for details. The password value is accessible
as :code:`main.password` and is used by :code:`main.remote_control()`
automatically.
Debugging kittens Debugging kittens
-------------------- --------------------
@@ -398,14 +348,6 @@ See `the code <https://github.com/kovidgoyal/kitty/tree/master/kittens/diff>`__
for the builtin :doc:`diff kitten </kittens/diff>` for examples of creating more for the builtin :doc:`diff kitten </kittens/diff>` for examples of creating more
options and keyboard shortcuts. options and keyboard shortcuts.
Developing builtin kittens for inclusion with kitty
----------------------------------------------------------
There is documentation for :doc:`developing-builtin-kittens` which are written in the Go
language.
.. _external_kittens: .. _external_kittens:
Kittens created by kitty users Kittens created by kitty users
@@ -418,10 +360,6 @@ Kittens created by kitty users
`smart-scroll <https://github.com/yurikhan/kitty-smart-scroll>`_ `smart-scroll <https://github.com/yurikhan/kitty-smart-scroll>`_
Makes the kitty scroll bindings work in full screen applications Makes the kitty scroll bindings work in full screen applications
`gattino <https://github.com/salvozappa/gattino>`__
Integrate kitty with an LLM to convert plain language prompts into shell commands.
:iss:`insert password <1222>` :iss:`insert password <1222>`
Insert a password from a CLI password manager, taking care to only do it at Insert a password from a CLI password manager, taking care to only do it at
a password prompt. a password prompt.

View File

@@ -1,119 +0,0 @@
Developing builtin kittens
=============================
Builtin kittens in kitty are written in the Go language, with small Python
wrapper scripts to define command line options and handle UI integration.
Getting started
-----------------------
To get started with creating a builtin kitten, one that will become part of kitty
and be available as ``kitten my-kitten``, create a directory named
:file:`my_kitten` in the :file:`kittens` directory. Then, in this directory
add three, files: :file:`__init__.py` (an empty file), :file:`__main__.py` and
:file:`main.go`.
Template for `main.py`
^^^^^^^^^^^^^^^^^^^^^^
The file :file:`main.py` contains the command line option definitions for your kitten. Change the actual options and help text below as needed.
.. code-block:: python
#!/usr/bin/env python
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import sys
# See the file kitty/cli.py in the kitty sourcecode for more examples of
# the syntax for defining options
OPTIONS = r'''
--some-string-option -s
default=my_default_value
Help text for a simple option taking a string value.
--some-boolean-option -b
type=bool-set
Help text for a boolean option defaulting to false.
--some-inverted-boolean-option
type=bool-unset
Help text for a boolean option defaulting to true.
--an-integer-option
type=int
default=13
bla bla
--an-enum-option
choices=a,b,c,d
default=a
This option can only take the values a, b, c, or d
'''.format
help_text = '''\
The introductory help text for your kitten.
Can contain multiple paragraphs with :bold:`bold`
:green:`colored`, :code:`code`, :link:`links <http://url>` etc.
formatting.
Option help strings can also use this formatting.
'''
# The usage string for your kitten
usage = 'TITLE [BODY ...]'
short_description = 'some short description of your kitten it will show up when running kitten without arguments to list all kittens`
if __name__ == '__main__':
raise SystemExit('This should be run as kitten my-kitten')
elif __name__ == '__doc__':
cd = sys.cli_docs # type: ignore
cd['usage'] = usage
cd['options'] = OPTIONS
cd['help_text'] = help_text
cd['short_desc'] = short_description
Template for `main.go`
^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: go
package my_kitten
import (
"fmt"
"kitty/tools/cli"
)
var _ = fmt.Print
func main(_ *cli.Command, opts *Options, args []string) (rc int, err error) {
// Here rc is the exit code for the kitten which should be 1 or higher if err is not nil
fmt.Println("Hello world!")
fmt.Println(args)
fmt.Println(fmt.Sprintf("%#v", opts))
return
}
func EntryPoint(parent *cli.Command) {
create_cmd(parent, main)
}
Edit :file:`tools/cmd/tool/main.go`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Add the entry point of the kitten into :file:`tools/cmd/tool/main.go`.
First, import the kitten into this file. To do this, add :code:`"kitty/kittens/my_kitten"` into the :code:`import ( ... )` section at the top.
Then, add ``my_kitten.EntryPoint(root)`` into ``func KittyToolEntryPoints(root *cli.Command)`` and you are done. After running make you should
be able to test your kitten by running::
kitten my-kitten

View File

@@ -64,17 +64,15 @@ Keyboard controls
=========================== =========================== =========================== ===========================
Action Shortcut Action Shortcut
=========================== =========================== =========================== ===========================
Quit :kbd:`Q` Quit :kbd:`Q`, :kbd:`Esc`
Scroll line up :kbd:`K`, :kbd:`Up` Scroll line up :kbd:`K`, :kbd:`Up`
Scroll line down :kbd:`J`, :kbd:`Down` Scroll line down :kbd:`J`, :kbd:`Down`
Scroll page up :kbd:`PgUp` Scroll page up :kbd:`PgUp`
Scroll page down :kbd:`PgDn` Scroll page down :kbd:`PgDn`
Scroll to top :kbd:`Home` Scroll to top :kbd:`Home`
Scroll to bottom :kbd:`End` Scroll to bottom :kbd:`End`
Scroll to next page :kbd:`Space`, :kbd:`PgDn`, :kbd:`Ctrl+F` Scroll to next page :kbd:`Space`, :kbd:`PgDn`
Scroll to previous page :kbd:`PgUp`, :kbd:`Ctrl+B` Scroll to previous page :kbd:`PgUp`
Scroll down half page :kbd:`Ctrl+D`
Scroll up half page :kbd:`Ctrl+U`
Scroll to next change :kbd:`N` Scroll to next change :kbd:`N`
Scroll to previous change :kbd:`P` Scroll to previous change :kbd:`P`
Increase lines of context :kbd:`+` Increase lines of context :kbd:`+`
@@ -83,7 +81,7 @@ All lines of context :kbd:`A`
Restore default context :kbd:`=` Restore default context :kbd:`=`
Search forwards :kbd:`/` Search forwards :kbd:`/`
Search backwards :kbd:`?` Search backwards :kbd:`?`
Clear search or exit :kbd:`Esc` Clear search :kbd:`Esc`
Scroll to next match :kbd:`>`, :kbd:`.` Scroll to next match :kbd:`>`, :kbd:`.`
Scroll to previous match :kbd:`<`, :kbd:`,` Scroll to previous match :kbd:`<`, :kbd:`,`
Copy selection to clipboard :kbd:`y` Copy selection to clipboard :kbd:`y`

View File

@@ -51,9 +51,6 @@ You can also :doc:`customize what actions are taken for different types of URLs
select that hint or press :kbd:`Enter` or :kbd:`Space` to select the empty select that hint or press :kbd:`Enter` or :kbd:`Space` to select the empty
hint. hint.
For mouse lovers, the hints kitten also allows you to click on any matched text to
select it instead of typing the hint character.
The hints kitten is very powerful to see more detailed help on its various The hints kitten is very powerful to see more detailed help on its various
options and modes of operation, see below. You can use these options to options and modes of operation, see below. You can use these options to
create mappings in :file:`kitty.conf` to select various different text create mappings in :file:`kitty.conf` to select various different text

View File

@@ -28,19 +28,19 @@ following contents:
.. code:: conf .. code:: conf
# Open any file with a fragment in vim, fragments are generated # Open any file with a fragment in vim, fragments are generated
# by the hyperlink-grep kitten and nothing else so far. # by the hyperlink_grep kitten and nothing else so far.
protocol file protocol file
fragment_matches [0-9]+ fragment_matches [0-9]+
action launch --type=overlay --cwd=current vim +${FRAGMENT} -- ${FILE_PATH} action launch --type=overlay --cwd=current vim +${FRAGMENT} ${FILE_PATH}
# Open text files without fragments in the editor # Open text files without fragments in the editor
protocol file protocol file
mime text/* mime text/*
action launch --type=overlay --cwd=current -- ${EDITOR} -- ${FILE_PATH} action launch --type=overlay --cwd=current ${EDITOR} ${FILE_PATH}
Now, run a search with:: Now, run a search with::
kitten hyperlinked-grep something kitten hyperlinked_grep something
Hold down the :kbd:`Ctrl+Shift` keys and click on any of the result lines, to Hold down the :kbd:`Ctrl+Shift` keys and click on any of the result lines, to
open the file in :program:`vim` at the matching line. If you use some editor open the file in :program:`vim` at the matching line. If you use some editor
@@ -51,7 +51,7 @@ accordingly. TO open links with the keyboard instead, use
Finally, add an alias to your shell's rc files to invoke the kitten as Finally, add an alias to your shell's rc files to invoke the kitten as
:command:`hg`:: :command:`hg`::
alias hg="kitten hyperlinked-grep" alias hg="kitten hyperlinked_grep"
You can now run searches with:: You can now run searches with::

View File

@@ -1,43 +0,0 @@
notify
==================================================
.. only:: man
Overview
--------------
Show pop-up system notifications.
.. highlight:: sh
.. versionadded:: 0.36.0
The notify kitten
The ``notify`` kitten can be used to show pop-up system notifications
from the shell. It even works over SSH. Using it is as simple as::
kitten notify "Good morning" Hello world, it is a nice day!
To add an icon, use::
kitten notify --icon-path /path/to/some/image.png "Good morning" Hello world, it is a nice day!
kitten notify --icon firefox "Good morning" Hello world, it is a nice day!
To be informed when the notification is activated::
kitten notify --wait-for-completion "Good morning" Hello world, it is a nice day!
Then, the kitten will wait till the notification is either closed or activated.
If activated, a ``0`` is printed to :file:`STDOUT`. You can press the
:kbd:`Esc` or :kbd:`Ctrl+c` keys to abort, closing the notification.
To add buttons to the notification::
kitten notify --wait-for-completion --button One --button Two "Good morning" Hello world, it is a nice day!
.. program:: kitty +kitten notify
.. tip:: Learn about the underlying :doc:`/desktop-notifications` escape code protocol.
.. include:: /generated/cli-kitten-notify.rst

View File

@@ -10,12 +10,12 @@ Draw a GPU accelerated dock panel on your desktop
You can use this kitten to draw a GPU accelerated panel on the edge of your You can use this kitten to draw a GPU accelerated panel on the edge of your
screen or as the desktop wallpaper, that shows the output from an arbitrary screen, that shows the output from an arbitrary terminal program.
terminal program.
It is useful for showing status information or notifications on your desktop It is useful for showing status information or notifications on your desktop
using terminal programs instead of GUI toolkits. using terminal programs instead of GUI toolkits.
.. figure:: ../screenshots/panel.png .. figure:: ../screenshots/panel.png
:alt: Screenshot, showing a sample panel :alt: Screenshot, showing a sample panel
:align: center :align: center
@@ -28,38 +28,21 @@ The screenshot above shows a sample panel that displays the current desktop and
window title as well as miscellaneous system information such as network window title as well as miscellaneous system information such as network
activity, CPU load, date/time, etc. activity, CPU load, date/time, etc.
.. versionadded:: 0.34.0
Support for Wayland
.. note:: .. note::
This kitten currently only works on X11 desktops and Wayland compositors This kitten currently only works on X11 desktops and Wayland compositors
that support the `wlr layer shell protocol that support the `wlr layer shell protocol
<https://wayland.app/protocols/wlr-layer-shell-unstable-v1#compositor-support>`__ <https://wayland.app/protocols/wlr-layer-shell-unstable-v1#compositor-support>`__
(which is almost all of them except the, as usual, crippled GNOME). (which is almost all of them except the usually crippled GNOME).
Using this kitten is simple, for example:: Using this kitten is simple, for example::
kitty +kitten panel sh -c 'printf "\n\n\nHello, world."; sleep 5s' kitty +kitten panel sh -c 'printf "\n\n\nHello, world."; sleep 5s'
This will show ``Hello, world.`` at the top edge of your screen for five This will show ``Hello, world.`` at the top edge of your screen for five
seconds. Here, the terminal program we are running is :program:`sh` with a script seconds. Here the terminal program we are running is :program:`sh` with a script
to print out ``Hello, world!``. You can make the terminal program as complex as to print out ``Hello, world!``. You can make the terminal program as complex as
you like, as demonstrated in the screenshot above. you like, as demonstrated in the screenshot above.
If you are on Wayland, you can, for instance run::
kitty +kitten panel --edge=background htop
to display htop as your desktop background. Remember this works in everything
but GNOME and also, in sway, you have to disable the background wallpaper as
sway renders that over the panel kitten surface.
There are projects that make use of this facility to implement generalised
panels and desktop components:
* `kitty panel <https://github.com/5hubham5ingh/kitty-panel>`__
* `pawbar <https://github.com/codelif/pawbar>`__
.. include:: ../generated/cli-kitten-panel.rst .. include:: ../generated/cli-kitten-panel.rst

View File

@@ -17,9 +17,8 @@ slow, since it requires a roundtrip to the terminal emulator and back.
If you want to do some of the same querying in your terminal program without If you want to do some of the same querying in your terminal program without
depending on the kitten, you can do so, by processing the same escape codes. depending on the kitten, you can do so, by processing the same escape codes.
Search `this page <https://invisible-island.net/xterm/ctlseqs/ctlseqs.html>`__ Search `this page <https://invisible-island.net/xterm/ctlseqs/ctlseqs.html>`__
for *XTGETTCAP* to see the syntax for the escape code. The kitty specific keys for *XTGETTCAP* to see the syntax for the escape code and read the source of
are all documented below, when sent via escape code they must be prefixed with this kitten to find the values of the keys for the various queries.
``kitty-query-``.
.. include:: ../generated/cli-kitten-query_terminal.rst .. include:: ../generated/cli-kitten-query_terminal.rst

View File

@@ -116,14 +116,14 @@ this could be achieved using the ssh kitten with :program:`zsh` and
hostname myserver-* hostname myserver-*
# Setup zsh to read its files from my-conf/zsh # Setup zsh to read its files from my-conf/zsh
env ZDOTDIR=$HOME/my-conf/zsh env ZDOTDIR $HOME/my-conf/zsh
copy --dest my-conf/zsh/.zshrc .zshrc copy --dest my-conf/zsh/.zshrc .zshrc
copy --dest my-conf/zsh/.zshenv .zshenv copy --dest my-conf/zsh/.zshenv .zshenv
# If you use other zsh init files add them in a similar manner # If you use other zsh init files add them in a similar manner
# Setup vim to read its config from my-conf/vim # Setup vim to read its config from my-conf/vim
env VIMINIT=$HOME/my-conf/vim/vimrc env VIMINIT $HOME/my-conf/vim/vimrc
env VIMRUNTIME=$HOME/my-conf/vim env VIMRUNTIME $HOME/my-conf/vim
copy --dest my-conf/vim .vim copy --dest my-conf/vim .vim
copy --dest my-conf/vim/vimrc .vimrc copy --dest my-conf/vim/vimrc .vimrc

View File

@@ -42,52 +42,6 @@ existing color settings in :file:`kitty.conf` so they do not interfere.
Once that's done, the kitten sends kitty a signal to make it reload its config. Once that's done, the kitten sends kitty a signal to make it reload its config.
.. note::
If you want to have some color settings in your :file:`kitty.conf` that the
theme kitten does not override, move them into a separate conf file and
``include`` it into kitty.conf. The include should be placed after the
inclusion of :file:`current-theme.conf` so that the settings in it override
conflicting settings from :file:`current-theme.conf`.
.. _auto_color_scheme:
Change color themes automatically when the OS switches between light and dark
--------------------------------------------------------------------------------
.. versionadded:: 0.38.0
You can have kitty automatically change its color theme when the OS switches
between dark, light and no-preference modes. In order to do this, run the theme
kitten as normal and at the final screen select the option to save your chosen
theme as either light, dark, or no-preference. Repeat until you have chosen
a theme for each of the three modes. Then, once you restart kitty, it will
automatically use your chosen themes depending on the OS color scheme.
This works by creating three files: :file:`dark-theme.auto.conf`,
:file:`light-theme.auto.conf` and :file:`no-preference-theme.auto.conf` in the
kitty config directory. When these files exist, kitty queries the OS for its color scheme
and uses the appropriate file. Note that the colors in these files override all other
colors, even those specified using the :option:`kitty --override` command line flag.
kitty will also automatically change colors when the OS color scheme changes,
for example, during night/day transitions.
When using these colors, you can still dynamically change colors, but the next
time the OS changes its color mode, any dynamic changes will be overridden.
.. note::
On the GNOME desktop, the desktop reports the color preference as no-preference
when the "Dark style" is not enabled. So use :file:`no-preference-theme.auto.conf` to
select colors for light mode on GNOME. You can manually enable light style
with ``gsettings set org.gnome.desktop.interface color-scheme prefer-light``
in which case GNOME will report the color scheme as light and kitty will use
:file:`light-theme.auto.conf`.
Using your own themes Using your own themes
----------------------- -----------------------

View File

@@ -11,7 +11,6 @@ Extend with kittens
kittens/diff kittens/diff
kittens/unicode_input kittens/unicode_input
kittens/themes kittens/themes
kittens/choose-fonts
kittens/hints kittens/hints
kittens/remote_file kittens/remote_file
kittens/hyperlinked_grep kittens/hyperlinked_grep
@@ -42,10 +41,6 @@ Some prominent kittens:
Preview and quick switch between over three hundred color themes. Preview and quick switch between over three hundred color themes.
:doc:`Fonts <kittens/choose-fonts>`
Preview, fine-tune and quick switch the fonts used by kitty.
:doc:`Hints <kittens/hints>` :doc:`Hints <kittens/hints>`
Select and open/paste/insert arbitrary text snippets such as URLs, Select and open/paste/insert arbitrary text snippets such as URLs,
filenames, words, lines, etc. from the terminal screen. filenames, words, lines, etc. from the terminal screen.

View File

@@ -114,83 +114,49 @@ Watching launched windows
The :option:`launch --watcher` option allows you to specify Python functions The :option:`launch --watcher` option allows you to specify Python functions
that will be called at specific events, such as when the window is resized or that will be called at specific events, such as when the window is resized or
closed. Note that you can also specify watchers that are loaded for all windows, closed. Simply specify the path to a Python module that specifies callback
via :opt:`watcher`. To create a watcher, specify the path to a Python module functions for the events you are interested in, for example:
that specifies callback functions for the events you are interested in, for
create :file:`~/.config/kitty/mywatcher.py` and use :option:`launch --watcher` = :file:`mywatcher.py`:
.. code-block:: python .. code-block:: python
# ~/.config/kitty/mywatcher.py from typing import Any, Dict
from typing import Any
from kitty.boss import Boss from kitty.boss import Boss
from kitty.window import Window from kitty.window import Window
def on_load(boss: Boss, data: dict[str, Any]) -> None: def on_resize(boss: Boss, window: Window, data: Dict[str, Any]) -> None:
# This is a special function that is called just once when this watcher
# module is first loaded, can be used to perform any initializztion/one
# time setup. Any exceptions in this function are printed to kitty's
# STDERR but otherwise ignored.
...
def on_resize(boss: Boss, window: Window, data: dict[str, Any]) -> None:
# Here data will contain old_geometry and new_geometry # Here data will contain old_geometry and new_geometry
# Note that resize is also called the first time a window is created
# which can be detected as old_geometry will have all zero values, in
# particular, old_geometry.xnum and old_geometry.ynum will be zero.
...
def on_focus_change(boss: Boss, window: Window, data: dict[str, Any])-> None: def on_focus_change(boss: Boss, window: Window, data: Dict[str, Any])-> None:
# Here data will contain focused # Here data will contain focused
...
def on_close(boss: Boss, window: Window, data: dict[str, Any])-> None: def on_close(boss: Boss, window: Window, data: Dict[str, Any])-> None:
# called when window is closed, typically when the program running in # called when window is closed, typically when the program running in
# it exits # it exits
...
def on_set_user_var(boss: Boss, window: Window, data: dict[str, Any]) -> None: def on_set_user_var(boss: Boss, window: Window, data: Dict[str, Any]) -> None:
# called when a "user variable" is set or deleted on a window. Here # called when a "user variable" is set or deleted on a window. Here
# data will contain key and value # data will contain key and value
...
def on_title_change(boss: Boss, window: Window, data: dict[str, Any]) -> None: def on_title_change(boss: Boss, window: Window, data: Dict[str, Any]) -> None:
# called when the window title is changed on a window. Here # called when the window title is changed on a window. Here
# data will contain title and from_child. from_child will be True # data will contain title and from_child. from_child will be True
# when a title change was requested via escape code from the program # when a title change was requested via escape code from the program
# running in the terminal # running in the terminal
...
def on_cmd_startstop(boss: Boss, window: Window, data: dict[str, Any]) -> None: def on_cmd_startstop(boss: Boss, window: Window, data: Dict[str, Any]) -> None:
# called when the shell starts/stops executing a command. Here # called when the shell starts/stops executing a command. Here
# data will contain is_start, cmdline and time. # data will contain is_start and time.
...
def on_color_scheme_preference_change(boss: Boss, window: Window, data: dict[str, Any]) -> None:
# called when the color scheme preference of this window changes from
# light to dark or vice versa. data contains is_dark and via_escape_code
# the latter will be true if the color scheme was changed via escape
# code received from the program running in the window
...
Every callback is passed a reference to the global ``Boss`` object as well as Every callback is passed a reference to the global ``Boss`` object as well as
the ``Window`` object the action is occurring on. The ``data`` object is a dict the ``Window`` object the action is occurring on. The ``data`` object is a dict
that contains event dependent data. You have full access to kitty internals in that contains event dependent data. Some useful methods and attributes for the
the watcher scripts, however kitty internals are not documented/stable so for ``Window`` object are: ``as_text(as_ans=False, add_history=False,
most things you are better off using the kitty :doc:`Remote control API </remote-control>`. add_wrap_markers=False, alternate_screen=False)`` with which you can get the
Simply call :code:`boss.call_remote_control()`, with the same arguments you contents of the window and its scrollback buffer. Similarly,
would pass to ``kitten @``. For example: ``window.child.pid`` is the PID of the processes that was launched
in the window and ``window.id`` is the internal kitty ``id`` of the window.
.. code-block:: python
def on_resize(boss: Boss, window: Window, data: dict[str, Any]) -> None:
# send some text to the resized window
boss.call_remote_control(window, ('send-text', f'--match=id:{window.id}', 'hello world'))
Run, ``kitten @ --help`` in a kitty terminal, to see all the remote control
commands available to you.
Finding executables Finding executables

View File

@@ -179,24 +179,16 @@ define a few extra key bindings in :file:`kitty.conf`::
map ctrl+up neighboring_window up map ctrl+up neighboring_window up
map ctrl+down neighboring_window down map ctrl+down neighboring_window down
# Set the bias of the split containing the currently focused window. The
# currently focused window will take up the specified percent of its parent
# window's size.
map ctrl+. layout_action bias 80
Windows can be resized using :ref:`window_resizing`. You can swap the windows Windows can be resized using :ref:`window_resizing`. You can swap the windows
in a split using the ``rotate`` action with an argument of ``180`` and rotate in a split using the ``rotate`` action with an argument of ``180`` and rotate
and swap with an argument of ``270``. and swap with an argument of ``270``.
This layout takes one option, ``split_axis`` that controls whether new windows This layout takes one option, ``split_axis`` that controls whether new windows
are placed into vertical or horizontal splits when a :option:`--location are placed into vertical or horizontal splits when a :option:`--location <launch
<launch --location>` is not specified. A value of ``horizontal`` (same as --location>` is not specified. A value of ``horizontal`` (same as
``--location=vsplit``) means when a new split is created the two windows will ``--location=vsplit``) means when a new split is created the two windows will be
be placed side by side and a value of ``vertical`` (same as placed side by side and a value of ``vertical`` (same as ``--location=hsplit``)
``--location=hsplit``) means the two windows will be placed one on top of the means the two windows will be placed one on top of the other. By default::
other. A value of ``auto`` means the axis of the split is chosen automatically
(same as ``--location=split``). By default::
enabled_layouts splits:split_axis=horizontal enabled_layouts splits:split_axis=horizontal

View File

@@ -201,8 +201,8 @@ In order to make this work, you need to configure your editor as show below:
In :file:`~/.vimrc` add: In :file:`~/.vimrc` add:
.. code-block:: vim .. code-block:: vim
let &t_ti = &t_ti . "\033]1337;SetUserVar=in_editor=MQo\007" let &t_ti = &t_ti . "\\033]1337;SetUserVar=in_editor=MQo\\007"
let &t_te = &t_te . "\033]1337;SetUserVar=in_editor\007" let &t_te = &t_te . "\\033]1337;SetUserVar=in_editor\\007"
.. tab:: neovim .. tab:: neovim

View File

@@ -1,38 +0,0 @@
Miscellaneous protocol extensions
==============================================
These are a few small protocol extensions kitty implements, primarily for use
by its own kitten, they are documented here for completeness.
Simple save/restore of all terminal modes
--------------------------------------------
XTerm has the XTSAVE/XTRESTORE escape codes to save and restore terminal
private modes. However, they require specifying an explicit list of modes to
save/restore. kitty extends this protocol to specify that when no modes are
specified, all side-effect free modes should be saved/restored. By side-effects
we mean things that can affect other terminal state such as cursor position or
screen contents. Examples of modes that have side effects are: `DECOM
<https://vt100.net/docs/vt510-rm/DECOM.html>`__ and `DECCOLM
<https://vt100.net/docs/vt510-rm/DECCOLM.html>`__.
This allows TUI applications to easily save and restore emulator state without
needing to maintain lists of modes.
Independent control of bold and faint SGR properties
-------------------------------------------------------
In common terminal usage, bold is set via SGR 1 and faint by SGR 2. However,
there is only one number to reset these attributes, SGR 22, which resets both.
There is no way to reset one and not the other. kitty uses 221 and 222 to reset
bold and faint independently.
kitty specific private escape codes
---------------------------------------
These are a family of escape codes used by kitty for various things including
remote control. They are all DCS (Device Control String) escape codes starting
with ``\x1b P @ kitty-`` (ignoring spaces present for clarity).

View File

@@ -1,61 +0,0 @@
#!/usr/bin/env python
# A sample script to process notifications. Save it as
# ~/.config/kitty/notifications.py
import subprocess
from kitty.notifications import NotificationCommand, Urgency
def log_notification(nc: NotificationCommand) -> None:
# Log notifications to /tmp/notifications-log.txt
with open('/tmp/notifications-log.txt', 'a') as log:
print(f'title: {nc.title}', file=log)
print(f'body: {nc.body}', file=log)
print(f'app: {nc.application_name}', file=log)
print(f'types: {nc.notification_types}', file=log)
print('\n', file=log)
def on_notification_activated(nc: NotificationCommand, which: int) -> None:
# do something when this notification is activated (clicked on)
# remember to assign this to the on_activation field in main()
pass
def main(nc: NotificationCommand) -> bool:
'''
This function should return True to filter out the notification
'''
log_notification(nc)
# filter out notifications with 'unwanted' in their titles
if 'unwanted' in nc.title.lower():
return True
# force the notification to be silent
nc.sound_name = 'silent'
# filter out notifications from the application badapp
if nc.application_name == 'badapp':
return True
# filter out low urgency notifications
if nc.urgency is Urgency.Low:
return True
# replace some bad text in the notification body
nc.body = nc.body.replace('bad text', 'good text')
# run a script if this notification is from myapp and has
# type foo, passing in the title and body as command line args
# to the script.
if nc.application_name == 'myapp' and 'foo' in nc.notification_types:
subprocess.Popen(['/path/to/my/script', nc.title, nc.body])
# do some arbitrary actions when this notification is activated
nc.on_activation = on_notification_activated
# dont filter out this notification
return False

View File

@@ -15,7 +15,7 @@ clicked. Let us illustrate with some examples, first. Create the file
# Open any image in the full kitty window by clicking on it # Open any image in the full kitty window by clicking on it
protocol file protocol file
mime image/* mime image/*
action launch --type=overlay kitten icat --hold -- ${FILE_PATH} action launch --type=overlay kitten icat --hold ${FILE_PATH}
Now, run ``ls --hyperlink=auto`` in kitty and click on the filename of an Now, run ``ls --hyperlink=auto`` in kitty and click on the filename of an
image, holding down :kbd:`ctrl+shift`. It will be opened over the current image, holding down :kbd:`ctrl+shift`. It will be opened over the current
@@ -44,7 +44,7 @@ action per entry if you like, for example:
# Tail a log file (*.log) in a new OS Window and reduce its font size # Tail a log file (*.log) in a new OS Window and reduce its font size
protocol file protocol file
ext log ext log
action launch --title ${FILE} --type=os-window tail -f -- ${FILE_PATH} action launch --title ${FILE} --type=os-window tail -f ${FILE_PATH}
action change_font_size current -2 action change_font_size current -2
@@ -64,9 +64,6 @@ some special variables, documented below:
``FRAGMENT`` ``FRAGMENT``
The fragment (unquoted), if any of the URL or the empty string. The fragment (unquoted), if any of the URL or the empty string.
``NETLOC``
The net location aka hostname (unquoted), if any of the URL or the empty string.
``URL_PATH`` ``URL_PATH``
The path, query and fragment portions of the URL, without any The path, query and fragment portions of the URL, without any
unquoting. unquoting.

View File

@@ -146,8 +146,6 @@ option in :file:`kitty.conf`. An example, showing all available commands:
launch --env FOO=BAR vim launch --env FOO=BAR vim
# Set the title for the next window # Set the title for the next window
launch --title "Chat with x" irssi --profile x launch --title "Chat with x" irssi --profile x
# Run a short lived command and see its output
launch --hold message-of-the-day
# Create a new tab # Create a new tab
# The part after new_tab is the optional tab title which will be displayed in # The part after new_tab is the optional tab title which will be displayed in
@@ -167,8 +165,6 @@ option in :file:`kitty.conf`. An example, showing all available commands:
os_window_size 80c 24c os_window_size 80c 24c
# Set the --class for the new OS window # Set the --class for the new OS window
os_window_class mywindow os_window_class mywindow
# Set the --name for the new OS window
os_window_name myname
# Change the OS window state to normal, fullscreen, maximized or minimized # Change the OS window state to normal, fullscreen, maximized or minimized
os_window_state normal os_window_state normal
launch sh launch sh
@@ -180,30 +176,12 @@ option in :file:`kitty.conf`. An example, showing all available commands:
focus_os_window focus_os_window
launch emacs launch emacs
# Create a complex layout using multiple splits. Creates two columns of
# windows with two windows in each column. The windows in the first column are
# split 50:50. In the second column the windows are not evenly split.
new_tab complex tab
layout splits
# First window, set a user variable on it so we can focus it later
launch --var window=first
# Create the second column by splitting the first window vertically
launch --location=vsplit
# Create the third window in the second column by splitting the second window horizontally
# Make it take 40% of the height instead of 50%
launch --location=hsplit --bias=40
# Go back to focusing the first window, so that we can split it
focus_matching_window var:window=first
# Create the final window in the first column
launch --location=hsplit
.. note:: .. note::
The :doc:`launch <launch>` command when used in a session file cannot create The :doc:`launch <launch>` command when used in a session file cannot create
new OS windows, or tabs. new OS windows, or tabs.
.. note:: .. note::
Environment variables of the form :code:`${NAME}` or :code:`$NAME` are Environment variables of the for :code:`${NAME}` or :code:`$NAME` are
expanded in the session file, except in the *arguments* (not options) to the expanded in the session file, except in the *arguments* (not options) to the
launch command. launch command.
@@ -246,7 +224,9 @@ All these actions can be customized in :file:`kitty.conf` as described
You can also customize what happens when clicking on :term:`hyperlinks` in You can also customize what happens when clicking on :term:`hyperlinks` in
kitty, having it open files in your editor, download remote files, open things kitty, having it open files in your editor, download remote files, open things
in your browser, etc. For details, see :doc:`here <open_actions>`. in your browser, etc.
For details, see :doc:`here <open_actions>`.
.. toctree:: .. toctree::
:hidden: :hidden:

View File

@@ -34,14 +34,6 @@ This is measured either with dedicated hardware, or software such as `Typometer
kitty with other terminal emulators on various systems show kitty has best in kitty with other terminal emulators on various systems show kitty has best in
class keyboard to screen latency. class keyboard to screen latency.
Note that to minimize latency at the expense of more energy usage, use the
following settings in kitty.conf::
input_delay 0
repaint_delay 2
sync_to_monitor no
wayland_enable_ime no
`Hardware based measurement on macOS `Hardware based measurement on macOS
<https://thume.ca/2020/05/20/making-a-latency-tester/>`__ show that kitty and <https://thume.ca/2020/05/20/making-a-latency-tester/>`__ show that kitty and
Apple's Terminal.app share the crown for best latency. These Apple's Terminal.app share the crown for best latency. These

View File

@@ -166,7 +166,7 @@ Legacy xterm compatibility
---------------------------- ----------------------------
The original xterm proposal for this escape code used shape names from the The original xterm proposal for this escape code used shape names from the
:file:`X11/cursorfont.h` header on X11 based systems. Terminal implementations file:`X11/cursorfont.h` header on X11 based systems. Terminal implementations
wishing to maintain compatibility with xterm can also implement these names as wishing to maintain compatibility with xterm can also implement these names as
aliases for the CSS based names defined in the :ref:`pointer_shape_names` table. aliases for the CSS based names defined in the :ref:`pointer_shape_names` table.

View File

@@ -27,7 +27,6 @@ please do so by opening issues in the `GitHub bug tracker
underlines underlines
graphics-protocol graphics-protocol
keyboard-protocol keyboard-protocol
text-sizing-protocol
file-transfer-protocol file-transfer-protocol
desktop-notifications desktop-notifications
pointer-shapes pointer-shapes
@@ -35,4 +34,3 @@ please do so by opening issues in the `GitHub bug tracker
color-stack color-stack
deccara deccara
clipboard clipboard
misc-protocol

View File

@@ -18,8 +18,8 @@ package, but note that some Linux distribution packages are woefully outdated.
|kitty| is available in a vast number of package repositories for macOS |kitty| is available in a vast number of package repositories for macOS
and Linux. and Linux.
.. image:: https://repology.org/badge/tiny-repos/kitty-terminal.svg .. image:: https://repology.org/badge/tiny-repos/kitty.svg
:target: https://repology.org/project/kitty-terminal/versions :target: https://repology.org/project/kitty/versions
:alt: Number of repositories kitty is available in :alt: Number of repositories kitty is available in
See :doc:`Configuring kitty <conf>` for help on configuring |kitty| and See :doc:`Configuring kitty <conf>` for help on configuring |kitty| and

View File

@@ -108,8 +108,7 @@ simpler :option:`kitty --single-instance` option, see ``kitty --help`` for that.
Remote control via a socket Remote control via a socket
-------------------------------- --------------------------------
To control kitty from outside kitty, it is necessary to setup a socket to First, start |kitty| as::
communicate with kitty. First, start |kitty| as::
kitty -o allow_remote_control=yes --listen-on unix:/tmp/mykitty kitty -o allow_remote_control=yes --listen-on unix:/tmp/mykitty
@@ -288,11 +287,10 @@ If you wish to run a more complex script, you can use::
In this script you can use ``kitten @`` to run as many remote In this script you can use ``kitten @`` to run as many remote
control commands as you like and process their output. control commands as you like and process their output.
:ac:`remote_control_script` is similar to the :ac:`remote_control_script` is really just an alias for the
:ac:`launch` command with ``--type=background --allow-remote-control``. :ac:`launch` command with ``--type=background --allow-remote-control``.
For more advanced usage, including fine grained permissions, setting For more advanced usage, including fine grained permissions, setting
env vars, command line interpolation, passing data to STDIN, etc. env vars, etc. see the docs for the :doc:`launch <launch>` command.
the :doc:`launch <launch>` command should be used.
.. note:: You do not need :opt:`allow_remote_control` to use these mappings, .. note:: You do not need :opt:`allow_remote_control` to use these mappings,
as they are not actual remote programs, but are simply a way to reuse the as they are not actual remote programs, but are simply a way to reuse the

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

View File

@@ -85,8 +85,6 @@ no-cwd
no-prompt-mark no-prompt-mark
Turn off marking of prompts. This disables jumping to prompt, browsing Turn off marking of prompts. This disables jumping to prompt, browsing
output of last command and click to move cursor functionality. output of last command and click to move cursor functionality.
Note that for the fish shell this does not take effect, since fish always
marks prompts.
no-complete no-complete
Turn off completion for the kitty command. Turn off completion for the kitty command.
@@ -421,72 +419,19 @@ The protocol used for marking the prompt is very simple. You should consider
adding it to your shell as a builtin. Many modern terminals make use of it, for adding it to your shell as a builtin. Many modern terminals make use of it, for
example: kitty, iTerm2, WezTerm, DomTerm example: kitty, iTerm2, WezTerm, DomTerm
Just before starting to draw the PS1 prompt send the escape code: Just before starting to draw the PS1 prompt send the escape code::
.. code-block:: none
<OSC>133;A<ST> <OSC>133;A<ST>
Just before starting to draw the PS2 prompt send the escape code: Just before starting to draw the PS2 prompt send the escape code::
.. code-block:: none
<OSC>133;A;k=s<ST> <OSC>133;A;k=s<ST>
Just before running a command/program, send the escape code: Just before running a command/program, send the escape code::
.. code-block:: none
<OSC>133;C<ST> <OSC>133;C<ST>
Optionally, when a command is finished its "exit status" can be reported as:
.. code-block:: none
<OSC>133;D;exit status as base 10 integer<ST>
Here ``<OSC>`` is the bytes ``0x1b 0x5d`` and ``<ST>`` is the bytes ``0x1b Here ``<OSC>`` is the bytes ``0x1b 0x5d`` and ``<ST>`` is the bytes ``0x1b
0x5c``. This is exactly what is needed for shell integration in kitty. For the 0x5c``. This is exactly what is needed for shell integration in kitty. For the
full protocol, that also marks the command region, see `the iTerm2 docs full protocol, that also marks the command region, see `the iTerm2 docs
<https://iterm2.com/documentation-escape-codes.html>`_. <https://iterm2.com/documentation-escape-codes.html>`_.
kitty additionally supports several extra fields for the ``<OSC>133;A`` command
to control its behavior, separated by semi-colons. They are:
``redraw=0``
this tells kitty that the shell will not redraw the prompt on
resize so it should not erase it
``special_key=1``
this tells kitty to use a special key instead of arrow keys
to move the cursor on mouse click. Useful if arrow keys have side-effects
like triggering auto complete. The shell integration script then binds the
special key, as needed.
``k=s``
this tells kitty that the secondary (PS2) prompt is starting at the
current line.
``click_events=1``
this tells kitty that the shell is capable of handling
mouse click events. kitty will thus send a click event to the shell when
the user clicks somewhere in the prompt. The shell can then move the cursor
to that position or perform some other appropriate action. Without this,
kitty will instead generate a number of fake key events to move the cursor
to the clicked location, which is not fully robust.
kitty also optionally supports sending the cmdline going to be executed with ``<OSC>133;C`` as:
.. code-block:: none
<OSC>133;C;cmdline=cmdline encoded by %q<ST>
or
<OSC>133;C;cmdline_url=cmdline as UTF-8 URL %-escaped text<ST>
Here, *encoded by %q* means the encoding produced by the %q format to printf in
bash and similar shells. Which is basically shell escaping with the addition of
using `ANSI C quoting
<https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html#ANSI_002dC-Quoting>`__
for control characters (``$''`` quoting).

View File

@@ -1,476 +0,0 @@
The text sizing protocol
==============================================
.. versionadded:: 0.40.0
Classically, because the terminal is a grid of equally sized characters, only
a single text size was supported in terminals, with one minor exception, some
characters were allowed to be rendered in two cells, to accommodate East Asian
square aspect ratio characters and Emoji. Here, by single text size we mean the
font size of all text on the screen is the same.
This protocol allows text to be displayed in the terminal in different sizes
both larger and smaller than the base text. It also solves the long standing
problem of robustly determining the width (in cells) a character should have.
Applications can interleave text of different sizes on the screen allowing for
typographic niceties like headlines, superscripts, etc.
Note that this protocol is fully backwards compatible, terminals that implement
it will continue to work just the same with applications that do not use it.
Because of this, it is not fully flexible in the font sizes it allows, as it
still has to work with the character cell grid based fundamental nature of the
terminal. Public discussion of this protocol is :iss:`here <8226>`.
Quickstart
--------------
Using this protocol to display different sized text is very simple, let's
illustrate with a few examples to give us a flavor:
.. code-block:: sh
printf "\e]_text_size_code;s=2;Double sized text\a\n\n"
printf "\e]_text_size_code;s=3;Triple sized text\a\n\n\n"
printf "\e]_text_size_code;n=1:d=2;Half sized text\a\n"
Note that the last example, of half sized text, has half height characters, but
they still each take one cell, this can be fixed with a little more work:
.. code-block:: sh
printf "\e]_text_size_code;n=1:d=2:w=1;Ha\a\e]66;n=1:d=2:w=1;lf\a\n"
The ``w=1`` mechanism allows the program to tell the terminal what width the text
should take. This not only fixes using smaller text but also solves the long
standing terminal ecosystem bugs caused by the client program not knowing how
many cells the terminal will render some text in.
The escape code
-----------------
There is a single escape code used by this protocol. It is sent by client
programs to the terminal emulator to tell it to render the specified text
at the specified size. It is an ``OSC`` code of the form::
<OSC> _text_size_code ; metadata ; text <terminator>
Here, ``OSC`` is the bytes ``ESC ] (0x1b 0x5b)``. The ``metadata`` is a colon
separated list of ``key=value`` pairs. The final part of the escape code is the
text which is simply plain text encoded as :ref:`safe_utf8`, the text must be
no longer than ``4096`` bytes. Longer strings than that must be broken up into
multiple escape codes. Spaces in this definition are for clarity only and
should be ignored. The ``terminator`` is either the byte ``BEL (0x7)`` or the
bytes ``ESC ST (0x1b 0x5c)``.
There are only a handful of metadata keys, defined in the table below:
.. csv-table:: The text sizing metadata keys
:header: "Key", "Value", "Default", "Description"
"s", "Integer from 1 to 7", "1", "The overall scale, the text will be rendered in a block of ``s * w`` by ``s`` cells"
"w", "Integer from 0 to 7", "0", "The width, in cells, in which the text should be rendered. When zero, the terminal should calculate the width as it would for normal text, splitting it up into scaled cells."
"n", "Integer from 0 to 15", "0", "The numerator for the fractional scale."
"d", "Integer from 0 to 15", "0", "The denominator for the fractional scale. Must be ``> n`` when non-zero."
"v", "Integer from 0 to 2", "0", "The vertical alignment to use for fractionally scaled text. ``0`` - top, ``1`` - bottom, ``2`` - centered"
"h", "Integer from 0 to 2", "0", "The horizontal alignment to use for fractionally scaled text. ``0`` - left, ``1`` - right, ``2`` - centered"
How it works
------------------
This protocol works by allowing the client program to tell the terminal to
render text in multiple cells. The terminal can then adjust the actual font
size used to render the specified text as appropriate for the specified space.
The space to render is controlled by four metadata keys, ``s (scale)``, ``w (width)``, ``n (numerator)``
and ``d (denominator)``. The most important are the ``s`` and ``w`` keys. The text
will be rendered in a block of ``s * w`` by ``s`` cells. A special case is ``w=0``
(the default), which means the terminal splits up the text into cells as it
would normally without this protocol, but now each cell is an ``s by s`` block of
cells instead. So, for example, if the text is ``abc`` and ``s=2`` the terminal would normally
split it into three cells::
│a│b│c│
But, because ``s=2`` it instead gets split as::
│a░│b░│c░│
│░░│░░│░░│
The terminal multiplies the font size by ``s`` when rendering these
characters and thus ends up rendering text at twice the base size.
When ``w`` is a non-zero value, it specifies the width in scaled cells of the
following text. Note that **all** the text in that escape code must be rendered
in ``s * w`` cells. When both ``s`` and ``w`` are present, the resulting multicell
contains all the text in the escape code rendered in a grid of ``(s * w, s)``
cells, i.e. the multicell is ``s*w`` cells wide and ``s`` cells high.
If the text does not fit, the terminal is free to do whatever it
feels is best, including truncating the text or downsizing the font size when
rendering it. It is up to client applications to use the ``w`` key wisely and not
try to render too much text in too few cells. When sending a string of text
with non zero ``w`` to the terminal emulator, the way to do it is to split up the
text into chunks that fit in ``w`` cells and send one escape code per chunk. So
for the string: ``cool-🐈`` the actual escape codes would be (ignoring the header
and trailers)::
w=1;c w=1;o w=1;o w=1;l w=1;- w=2:🐈
Note, in particular, how the last character, the cat emoji, ``🐈`` has ``w=2``.
In practice client applications can assume that terminal emulators get the
width of all ASCII characters correct and use the ``w=0`` form for efficient
transmission, so that the above becomes::
cool- w=2:🐈
The use of non-zero ``w`` should mainly be restricted to non-ASCII characters and
when using fractional scaling, as described below.
.. note:: Text sizes specified by scale are relative to the base font size,
thus if the base font size is changed, these sizes are changed as well.
So if the terminal emulator is using a base font size of ``11pt``, then
``s=2`` will be rendered in approximately ``22pt`` (approx. because the
terminal may need to slightly adjust font size to ensure it fits as not all
fonts scale sizes linearly). If the user changes the base font size of the
terminal emulator to ``12pt`` then the scaled font size becomes ``~24pt``
and so on.
Fractional scaling
^^^^^^^^^^^^^^^^^^^^^^^
Using the main scale parameter (``s``) gives us only 7 font sizes. Fortunately,
this protocol allows specifying fractional scaling, fractional scaling is
applied on top of the main scale specified by ``s``. It allows niceties like:
* Normal sized text but with half a line of blank space above and half a line below (``s=2:n=1:d=2:v=2``)
* Superscripts (``n=1:d=2``)
* Subscripts (``n=1:d=2:v=1``)
* ...
The fractional scale **does not** affect the number of cells the text occupies,
instead, it just adjusts the rendered font size within those cells.
The fraction is specified using an integer numerator and denominator (``n`` and
``d``). In addition, by using the ``v`` key one can vertically align the
fractionally scaled text at top, bottom or middle. Similarly, the ``h`` key
does horizontal alignment — left, right or centered.
When using fractional scaling one often wants to fit more than a single
character per cell. To accommodate that, there is the ``w`` key. This specifies
the number of cells in which to render the text. For example, for a superscript
one would typically split the string into pairs of characters and use the
following for each pair::
OSC _text_size_code ; n=1:d=2:w=1 ; ab <terminator>
... repeat for each pair of characters
Fixing the character width issue for the terminal ecosystem
---------------------------------------------------------------------
Terminals create user interfaces using text displayed in a cell grid. For
terminal software that creates sophisticated user interfaces it is particularly
important that the client program running in the terminal and the terminal
itself agree on how many cells a particular string should be rendered in. If
the two disagree, then the entire user interface can be broken, leading to
catastrophic failures.
Fundamentally, this is a co-ordination problem. Both the client program and the
terminal have to somehow share the same database of character properties and
the same algorithm for computing string lengths in cells based on that shared
database. Sadly, there is no such shared database in reality. The closest we
have is the Unicode standard. Unfortunately, the Unicode standard has a new
version almost every year and actually changes the width assigned to some
characters in different versions. Furthermore, to actually get the "correct"
width for a string using that standard one has to do grapheme segmentation,
which is a :ref:`complex algorithm, specified below <gseg>`.
Expecting all terminals and all terminal programs to have both up-to-date
character databases and a bug free implementation of this algorithm is not
realistic.
So instead, this protocol solves the issue robustly by removing the
co-ordination problem and putting only one actor in charge of determining
string width. The client becomes responsible for doing whatever level of
grapheme segmentation it is comfortable with using whatever Unicode database is
at its disposal and then it can transmit the segmented string to the terminal
with the appropriate ``w`` values so that the terminal renders the text in the
exact number of cells the client expects.
.. note::
It is possible for a terminal to implement only the width part of this spec
and ignore the scale part. This escape code works with only the `w` key as
well, as a means of specifying how many cells each piece of text occupies.
In such cases ``s`` defaults to 1.
See the section on :ref:`detect_text_sizing` on how client applications can
query for terminal emulator support.
Wrapping and overwriting behavior
-------------------------------------
If the multicell block (``s * w by s`` cells) is larger than the screen size in either
dimension, the terminal must discard the character. Note that in particular
this means that resizing a terminal screen so that it is too small to fit a
multicell character can cause the character to be lost.
When drawing a multicell character, if wrapping is enabled (DECAWM is set) and
the character's width (``s * w``) does not fit on the current line, the cursor is
moved to the start of the next line and the character is drawn there.
If wrapping is disabled and the character's width does not fit on the current
line, the cursor is moved back as far as needed to fit ``s * w`` cells and then
the character is drawn, following the overwriting rules described below.
When drawing text either normal text or text specified via this escape code,
and this text would overwrite an existing multicell character, the following
rules must be followed, in decreasing order of precedence:
#. If the text is a combining character it is added to the existing multicell
character
#. If the text will overwrite the top-left cell of the multicell character, the
entire multicell character must be erased
#. If the text will overwrite any cell in the topmost row of the multicell
character, the entire multicell character must be replaced by spaces (this
rule is present for backwards compatibility with how overwriting works for
wide characters)
#. If the text will overwrite cells from a row after the first row, then cursor should be moved past the
cells of the multicell character on that row and only then the text should be
written. Note that this behavior is independent of the value of DECAWM. This
is done for simplicity of implementation.
The skipping behavior of the last rule can be complex requiring the terminal to
skip over lots of cells, but it is needed to allow wrapping in the presence of
multicell characters that extend over more than a single line.
.. _detect_text_sizing:
Detecting if the terminal supports this protocol
-----------------------------------------------------
To detect support for this protocol use the `CPR (Cursor Position Report)
<https://vt100.net/docs/vt510-rm/CPR.html>`__ escape code. Send a ``CPR``
followed by ``\e]_text_size_code;w=2; \a`` which will draw a space character in
two cells, followed by another ``CPR``. Then send ``\e]_text_size_code;s=2; \a``
which will draw a space in a ``2 by 2`` block of cells, followed by another
``CPR``.
Then wait for the three responses from the terminal to the three CPR queries.
If the cursor position in the three responses is the same, the terminal does
not support this protocol at all, if the second response has a different cursor
position then the width part is supported and if the third response has yet
another position, the scale part is supported.
Interaction with other terminal controls
--------------------------------------------------
This protocol does not change the character grid based nature of the terminal.
Most terminal controls assume one character per cell so it is important to
specify how these controls interact with the multicell characters created by
this protocol.
Cursor movement
^^^^^^^^^^^^^^^^^^^
Cursor movement is unaffected by multicell characters, all cursor movement
commands move the cursor position by single cell increments, as has always been
the case for terminals. This means that the cursor can be placed at any
individual single cell inside a larger multicell character.
When a multicell character is created using this protocol, the cursor moves
`s * w` cells to the right, in the same row it was in.
Terminals *should* display a large cursor covering the entire multicell block
when the actual cursor position is on any cell within the block. Block cursors
cover all the cells of the multicell character, bar cursors appear in all the
cells in the first column of the character and so on.
Editing controls
^^^^^^^^^^^^^^^^^^^^^^^^^
There are many controls used to edit existing screen content such as
inserting characters, deleting characters and lines, etc. These were all
originally specified for the one character per cell paradigm. Here we specify
their interactions with multicell characters.
**Insert characters** (``CSI @`` aka ``ICH``)
When inserting ``n`` characters at cursor position ``x, y`` all characters
after ``x`` on line ``y`` are supposed to be right shifted. This means
that any multi-line character that intersects with the cells on line ``y`` at ``x``
and beyond must be erased. Any single line multicell character that is
split by the cells at ``x`` and ``x + n - 1`` must also be erased.
**Delete characters** (``CSI P`` aka ``DCH``)
When deleting ``n`` characters at cursor position ``x, y`` all characters
after ``x`` on line ``y`` are supposed to be left shifted. This means
that any multi-line character that intersects with the cells on line ``y`` at ``x``
and beyond must be erased. Any single line multicell character that is
split by the cells at ``x`` and ``x + n - 1`` must also be erased.
**Erase characters** (``CSI X`` aka ``ECH``)
When erasing ``n`` characters at cursor position ``x, y`` the ``n`` cells
starting at ``x`` are supposed to be cleared. This means that any multicell
character that intersects with the ``n`` cells starting at ``x`` must be
erased.
**Erase display** (``CSI J`` aka ``ED``)
Any multicell character intersecting with the erased region of the screen
must be erased. When using mode ``22`` the contents of the screen are first
copied into the history, including all multicell characters.
**Erase in line** (``CSI K`` aka ``EL``)
Works just like erase characters above. Any multicell character
intersecting with the erased cells in the line is erased.
**Insert lines** (``CSI L`` aka ``IL``)
When inserting ``n`` lines at cursor position ``y`` any multi-line
characters that are split at the line ``y`` must be erased. A split happens
when the second or subsequent row of the multi-line character is on the line
``y``. The insertion causes ``n`` lines to be removed from the bottom of
the screen, any multi-line characters are split at the bottom of the screen
must be erased. A split is when any row of the multi-line character except
the last row is on the last line of the screen after the insertion of ``n``
lines.
**Delete lines** (``CSI M`` aka ``DL``)
When deleting ``n`` lines at cursor position ``y`` any multicell character
that intersects the deleted lines must be erased.
.. _gseg:
The algorithm for splitting text into cells
------------------------------------------------
.. note::
kitty comes with a utility to test terminal compliance with this algorithm.
Install kitty and run: ``kitten __width_test__`` in any terminal to test it.
This uses tests published by the Unicode consortium, `GraphemeBreakTest.txt
<https://www.unicode.org/Public/UCD/latest/ucd/auxiliary/GraphemeBreakTest.txt>`__.
.. warning::
This algorithm is under public discussion in :iss:`8533`. Until that issue
is closed, it is subject to change based on feedback from the community.
Additionally, in the future if the Unicode standard changes in ways that
affect this algorithm, it will be updated. Currently the algorithm is based
on Unicode version 16.
Here, we specify how a terminal must split up text into cells, where a cell is
a width one unit in the character grid the terminal displays.
The basis for the algorithm is the
`Grapheme segmentation algorithm <https://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries>`__
from the Unicode standard. However, that algorithm alone is insufficient to
fully specify text handling for terminals. The full algorithm is specified
below. When a terminal receives a Unicode character:
#. First check if the character is an ASCII control code, and handle it
appropriately. ASCII control codes are the characters less than 32 and the
character 127 (DEL). The NUL character (0) must be discarded.
#. Next, check if the character is *invalid*, and if it is, discard it
and finish processing. Invalid characters are characters with Unicode category :code:`Cc or Cs`
and 66 additional characters: :code:`[0xfdd0, 0xfdef]`, :code:`[0xfffe, 0x10ffff-1, 0x10000]`
and :code:`[0xffff, 0x10ffff, 0x10000]`.
#. Next, check if there is a previous cell before the
current cursor position. This means either the cursor is at x > 0 in which
case the previous cell is at x-1 on the same line, or the previous cell is
the last cell of the previous line, provided there is no line break
between the previous and current lines.
#. Next, calculate the width in cells of the received
character, which can be 0, 1, or 2 depending on the character properties in
the Unicode standard.
#. If there is no previous cell and the character width is zero, the character
is discarded and processing of the character is finished.
#. If there is a previous cell, the
`Grapheme segmentation algorithm UAX29-C1-1 <https://www.unicode.org/reports/tr29/#C1-1>`__
is used to determine if there is a grapheme boundary between the previous cell and the current character.
#. If there is no boundary the current character is added to the previous
cell and processing of the character is finished. See the :ref:`var_select`
section below for handling of Unicode Variation selectors.
#. If there is a boundary, but the width of the current character is zero
it is added to the previous cell and processing is finished.
#. The character is added to the current cell and the cursor is moved forward
(right) by either 1 or 2 cells depending on the width of the character.
It remains to specify how to calculate the width in cells of a Unicode
character. To do this, characters are divided into various classes, as
described by the rules below, in order of decreasing priority:
.. note::
Notation: :code:`[start, stop, step]` means the integers from :code:`start`
to :code:`stop` in increments of :code:`step`. When the step is not
specified, it defaults to one.
#. *Regional indicators*: 26 characters starting at :code:`0x1F1E6`. These all
have width 2
#. *Doublewidth*: Parse `EastAsianWidth.txt
<https://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt>`__ from
the Unicode standard. All characters marked :code:`W` or :code:`F` have
width two. All characters in the following ranges have width two *unless*
they are marked as :code:`A` in :code:`EastAsianWidth.txt`: :code:`[0x3400,
0x4DBF], [0x4E00, 0x9FFF], [0xF900, 0xFAFF], [0x20000, 0x2FFFD], [0x30000, 0x3FFFD]`
#. *Wide emoji*: Parse `emoji-sequences.txt
<https://www.unicode.org/Public/emoji/latest/emoji-sequences.txt>`__ from
the Unicode standard. All :code:`Basic_Emoji` have width two unless they are
followed by :code:`FE0F` in the file. The leading copdepoints in all
:code:`RGI_Emoji_Modifier_Sequence` and :code:`RGI_Emoji_Tag_Sequence` have width two.
All codepoints in :code:`RGI_Emoji_Flag_Sequence` have width two.
#. *Marks*: These are all zero width characters. They are characters with Unicode
categories whose first letter is :code:`M` or :code:`S`. Additionally,
characters with Unicode category: :code:`Cf`. Finally, they include
all modifier codepoints from :code:`RGI_Emoji_Modifier_Sequence` in the
*Wide emoji* rule above.
#. All remaining codepoints have a width of one cell.
.. _var_select:
Unicode variation selectors
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
There are two codepoints (:code:`U+FE0E` and :code:`U+FE0F`) that can actually
alter the width of the previous codepoint. When adding a codepoint to the
previous cell these have to be handled specially.
``U+FE0E`` - Variation Selector 15
When the previous cell has width two and the last character in the previous
cell is one of the ``Basic_Emoji`` codepoints from the *Wide emoji* rule above
that is *not* followed by ``FEOF`` then the width of the previous cell is
decreased to one.
``U+FE0F`` - Variation Selector 16
When the previous cell has width one and the last character in the previous
cell is one of the ``Basic_Emoji`` codepoints from the *Wide emoji* rule above
that is followed by ``FEOF`` then the width of the
previous cell is increased to two.
Note that the rule for ``U+FE0E`` is particularly problematic for terminals as
it means that the width of a string cannot be determined without knowing the
width of the screen it will be rendered on. This is because when there is only
one cell left on the current line and a wide emoji is received it wraps onto
the next line. If subsequently a ``U+FE0E`` is received, the emoji becomes one
cell wide but it is *not* moved back to the previous line.
To avoid this issue, it is recommended applications detect when ``U+FE0E`` is
present and in such cases use the width part of the text sizing protocol
to control rendering.

View File

@@ -1,24 +0,0 @@
package kitty
import (
_ "embed"
"encoding/json"
"fmt"
)
var _ = fmt.Print
//go:embed kitty_tests/GraphemeBreakTest.json
var grapheme_break_test_data []byte
type GraphemeBreakTest struct {
Data []string `json:"data"`
Comment string `json:"comment"`
}
func LoadGraphemeBreakTests() (ans []GraphemeBreakTest, err error) {
if err := json.Unmarshal(grapheme_break_test_data, &ans); err != nil {
return nil, fmt.Errorf("Failed to parse GraphemeBreakTest JSON with error: %s", err)
}
return
}

View File

@@ -4,9 +4,10 @@
import os import os
import sys import sys
from typing import List
def main(args: list[str]=sys.argv) -> None: def main(args: List[str]=sys.argv) -> None:
os.chdir(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) os.chdir(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.insert(0, os.getcwd()) sys.path.insert(0, os.getcwd())
if len(args) == 1: if len(args) == 1:

View File

@@ -5,18 +5,18 @@ import os
import subprocess import subprocess
import sys import sys
from collections import defaultdict from collections import defaultdict
from typing import Any, DefaultDict, Union from typing import Any, DefaultDict, Dict, FrozenSet, List, Tuple, Union
if __name__ == '__main__' and not __package__: if __name__ == '__main__' and not __package__:
import __main__ import __main__
__main__.__package__ = 'gen' __main__.__package__ = 'gen'
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
KeymapType = dict[str, tuple[str, Union[frozenset[str], str]]] KeymapType = Dict[str, Tuple[str, Union[FrozenSet[str], str]]]
def resolve_keys(keymap: KeymapType) -> DefaultDict[str, list[str]]: def resolve_keys(keymap: KeymapType) -> DefaultDict[str, List[str]]:
ans: DefaultDict[str, list[str]] = defaultdict(list) ans: DefaultDict[str, List[str]] = defaultdict(list)
for ch, (attr, atype) in keymap.items(): for ch, (attr, atype) in keymap.items():
if isinstance(atype, str) and atype in ('int', 'uint'): if isinstance(atype, str) and atype in ('int', 'uint'):
q = atype q = atype
@@ -45,7 +45,7 @@ def parse_key(keymap: KeymapType) -> str:
return ' \n'.join(lines) return ' \n'.join(lines)
def parse_flag(keymap: KeymapType, type_map: dict[str, Any], command_class: str) -> str: def parse_flag(keymap: KeymapType, type_map: Dict[str, Any], command_class: str) -> str:
lines = [] lines = []
for ch in type_map['flag']: for ch in type_map['flag']:
attr, allowed_values = keymap[ch] attr, allowed_values = keymap[ch]
@@ -63,14 +63,14 @@ def parse_flag(keymap: KeymapType, type_map: dict[str, Any], command_class: str)
return ' \n'.join(lines) return ' \n'.join(lines)
def parse_number(keymap: KeymapType) -> tuple[str, str]: def parse_number(keymap: KeymapType) -> Tuple[str, str]:
int_keys = [f'I({attr})' for attr, atype in keymap.values() if atype == 'int'] int_keys = [f'I({attr})' for attr, atype in keymap.values() if atype == 'int']
uint_keys = [f'U({attr})' for attr, atype in keymap.values() if atype == 'uint'] uint_keys = [f'U({attr})' for attr, atype in keymap.values() if atype == 'uint']
return '; '.join(int_keys), '; '.join(uint_keys) return '; '.join(int_keys), '; '.join(uint_keys)
def cmd_for_report(report_name: str, keymap: KeymapType, type_map: dict[str, Any], payload_allowed: bool, payload_is_base64: bool) -> str: def cmd_for_report(report_name: str, keymap: KeymapType, type_map: Dict[str, Any], payload_allowed: bool) -> str:
def group(atype: str, conv: str) -> tuple[str, str]: def group(atype: str, conv: str) -> Tuple[str, str]:
flag_fmt, flag_attrs = [], [] flag_fmt, flag_attrs = [], []
cv = {'flag': 'c', 'int': 'i', 'uint': 'I'}[atype] cv = {'flag': 'c', 'int': 'i', 'uint': 'I'}[atype]
for ch in type_map[atype]: for ch in type_map[atype]:
@@ -85,20 +85,12 @@ def cmd_for_report(report_name: str, keymap: KeymapType, type_map: dict[str, Any
fmt = f'{flag_fmt} {uint_fmt} {int_fmt}' fmt = f'{flag_fmt} {uint_fmt} {int_fmt}'
if payload_allowed: if payload_allowed:
ans = [f'REPORT_VA_COMMAND("K s {{{fmt} ss#}}", self->window_id, "{report_name}",\n'] ans = [f'REPORT_VA_COMMAND("K s {{{fmt} sI}} y#", self->window_id, "{report_name}", ']
else: else:
ans = [f'REPORT_VA_COMMAND("K s {{{fmt}}}", self->window_id, "{report_name}",\n'] ans = [f'REPORT_VA_COMMAND("K s {{{fmt}}}", self->window_id, "{report_name}", ']
if flag_attrs: ans.append(',\n '.join((flag_attrs, uint_attrs, int_attrs)))
ans.append(f'{flag_attrs},\n')
if uint_attrs:
ans.append(f'{uint_attrs},\n')
if int_attrs:
ans.append(f'{int_attrs},\n')
if payload_allowed: if payload_allowed:
if payload_is_base64: ans.append(', "payload_sz", g.payload_sz, parser_buf, g.payload_sz')
ans.append('"", (char*)parser_buf, g.payload_sz')
else:
ans.append('"", (char*)parser_buf + payload_start, g.payload_sz')
ans.append(');') ans.append(');')
return '\n'.join(ans) return '\n'.join(ans)
@@ -110,63 +102,46 @@ def generate(
keymap: KeymapType, keymap: KeymapType,
command_class: str, command_class: str,
initial_key: str = 'a', initial_key: str = 'a',
payload_allowed: bool = True, payload_allowed: bool = True
payload_is_base64: bool = True,
start_parsing_at: int = 1,
field_sep: str = ',',
) -> str: ) -> str:
type_map = resolve_keys(keymap) type_map = resolve_keys(keymap)
keys_enum = enum(keymap) keys_enum = enum(keymap)
handle_key = parse_key(keymap) handle_key = parse_key(keymap)
flag_keys = parse_flag(keymap, type_map, command_class) flag_keys = parse_flag(keymap, type_map, command_class)
int_keys, uint_keys = parse_number(keymap) int_keys, uint_keys = parse_number(keymap)
report_cmd = cmd_for_report(report_name, keymap, type_map, payload_allowed, payload_is_base64) report_cmd = cmd_for_report(report_name, keymap, type_map, payload_allowed)
extra_init = ''
if payload_allowed: if payload_allowed:
payload_after_value = "case ';': state = PAYLOAD; break;" payload_after_value = "case ';': state = PAYLOAD; break;"
payload = ', PAYLOAD' payload = ', PAYLOAD'
if payload_is_base64: payload_case = f'''
payload_case = f''' case PAYLOAD: {{
case PAYLOAD: {{ sz = parser_buf_pos - pos;
sz = parser_buf_pos - pos; g.payload_sz = MAX(BUF_EXTRA, sz);
if (!base64_decode8(parser_buf + pos, sz, parser_buf, &g.payload_sz)) {{
g.payload_sz = MAX(BUF_EXTRA, sz); g.payload_sz = MAX(BUF_EXTRA, sz);
if (!base64_decode8(parser_buf + pos, sz, parser_buf, &g.payload_sz)) {{ REPORT_ERROR("Failed to parse {command_class} command payload with error: \
g.payload_sz = MAX(BUF_EXTRA, sz); invalid base64 data in chunk of size: %zu with output buffer size: %zu", sz, g.payload_sz); return; }}
REPORT_ERROR("Failed to parse {command_class} command payload with error: \ pos = parser_buf_pos;
invalid base64 data in chunk of size: %zu with output buffer size: %zu", sz, g.payload_sz); return; }} }}
pos = parser_buf_pos; break;
}} break; '''
''' callback = f'{callback_name}(self->screen, &g, parser_buf)'
callback = f'{callback_name}(self->screen, &g, parser_buf)'
else:
payload_case = '''
case PAYLOAD: {
sz = parser_buf_pos - pos;
payload_start = pos;
g.payload_sz = sz;
pos = parser_buf_pos;
} break;
'''
extra_init = 'size_t payload_start = 0;'
callback = f'{callback_name}(self->screen, &g, parser_buf + payload_start)'
else: else:
payload_after_value = payload = payload_case = '' payload_after_value = payload = payload_case = ''
callback = f'{callback_name}(self->screen, &g)' callback = f'{callback_name}(self->screen, &g)'
return f''' return f'''
#include "base64.h" #include "base64.h"
static inline void static inline void
{function_name}(PS *self, uint8_t *parser_buf, const size_t parser_buf_pos) {{ {function_name}(PS *self, uint8_t *parser_buf, const size_t parser_buf_pos) {{
unsigned int pos = {start_parsing_at}; unsigned int pos = 1;
{extra_init}
enum PARSER_STATES {{ KEY, EQUAL, UINT, INT, FLAG, AFTER_VALUE {payload} }}; enum PARSER_STATES {{ KEY, EQUAL, UINT, INT, FLAG, AFTER_VALUE {payload} }};
enum PARSER_STATES state = KEY, value_state = FLAG; enum PARSER_STATES state = KEY, value_state = FLAG;
{command_class} g = {{0}}; static {command_class} g;
unsigned int i, code; unsigned int i, code;
uint64_t lcode; int64_t accumulator; uint64_t lcode; int64_t accumulator;
bool is_negative; (void)is_negative; bool is_negative;
memset(&g, 0, sizeof(g));
size_t sz; size_t sz;
{keys_enum} {keys_enum}
enum KEYS key = '{initial_key}'; enum KEYS key = '{initial_key}';
@@ -239,10 +214,10 @@ static inline void
case AFTER_VALUE: case AFTER_VALUE:
switch (parser_buf[pos++]) {{ switch (parser_buf[pos++]) {{
default: default:
REPORT_ERROR("Malformed {command_class} control block, expecting a {field_sep} or semi-colon after a value, found: 0x%x", REPORT_ERROR("Malformed {command_class} control block, expecting a comma or semi-colon after a value, found: 0x%x",
parser_buf[pos - 1]); parser_buf[pos - 1]);
return; return;
case '{field_sep}': case ',':
state = KEY; state = KEY;
break; break;
{payload_after_value} {payload_after_value}
@@ -281,7 +256,7 @@ def write_header(text: str, path: str) -> None:
subprocess.check_call(['clang-format', '-i', path]) subprocess.check_call(['clang-format', '-i', path])
def parsers() -> None: def graphics_parser() -> None:
flag = frozenset flag = frozenset
keymap: KeymapType = { keymap: KeymapType = {
'a': ('action', flag('tTqpdfac')), 'a': ('action', flag('tTqpdfac')),
@@ -316,22 +291,10 @@ def parsers() -> None:
} }
text = generate('parse_graphics_code', 'screen_handle_graphics_command', 'graphics_command', keymap, 'GraphicsCommand') text = generate('parse_graphics_code', 'screen_handle_graphics_command', 'graphics_command', keymap, 'GraphicsCommand')
write_header(text, 'kitty/parse-graphics-command.h') write_header(text, 'kitty/parse-graphics-command.h')
keymap = {
'w': ('width', 'uint'),
's': ('scale', 'uint'),
'n': ('subscale_n', 'uint'),
'd': ('subscale_d', 'uint'),
'v': ('vertical_align', 'uint'),
'h': ('horizontal_align', 'uint'),
}
text = generate(
'parse_multicell_code', 'screen_handle_multicell_command', 'multicell_command', keymap, 'MultiCellCommand',
payload_is_base64=False, start_parsing_at=0, field_sep=':')
write_header(text, 'kitty/parse-multicell-command.h')
def main(args: list[str]=sys.argv) -> None: def main(args: List[str]=sys.argv) -> None:
parsers() graphics_parser()
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -1,64 +0,0 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2025, Kovid Goyal <kovid at kovidgoyal.net>
import os
from typing import NamedTuple
class BitField(NamedTuple):
name: str
bits: int
def typename_for_bitsize(bits: int) -> str:
if bits <= 8:
return 'uint8'
if bits <= 16:
return 'uint16'
if bits <= 32:
return 'uint32'
return 'uint64'
def make_bitfield(dest: str, typename: str, *fields_: str, add_package: bool = True) -> tuple[str, str]:
output_path = os.path.join(dest, f'{typename.lower()}_generated.go')
ans = [f'package {os.path.basename(dest)}', '']
a = ans.append
if not add_package:
del ans[0]
def fieldify(spec: str) -> BitField:
name, num = spec.partition(' ')[::2]
return BitField(name, int(num))
fields = tuple(map(fieldify, fields_))
total_size = sum(x.bits for x in fields)
if total_size > 64:
raise ValueError(f'Total size of bit fields: {total_size} for {typename} is larger than 64 bits')
a(f'// Total number of bits used: {total_size}')
itype = typename_for_bitsize(total_size)
a(f'type {typename} {itype}')
a('')
shift = 0
for bf in reversed(fields):
tn = typename_for_bitsize(bf.bits)
mask = '0b' + '1' * bf.bits
a(f'func (s {typename}) {bf.name.capitalize()}() {tn} {{') # }}
if shift:
a(f' return {tn}((s >> {shift}) & {mask})')
else:
a(f' return {tn}(s & {mask})')
a('}')
a('')
a(f'func (s *{typename}) Set_{bf.name}(val {tn}) {{') # }}
if shift:
a(f' *s &^= {mask} << {shift}')
a(f' *s |= {typename}(val&{mask}) << {shift}')
else:
a(f' *s &^= {mask}')
a(f' *s |= {typename}(val & {mask})')
a('}')
a('')
shift += bf.bits
return output_path, '\n'.join(ans)

View File

@@ -6,6 +6,7 @@ import os
import re import re
import subprocess import subprocess
import sys import sys
from typing import List
from kitty.conf.generate import write_output from kitty.conf.generate import write_output
@@ -15,7 +16,7 @@ if __name__ == '__main__' and not __package__:
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
def patch_color_list(path: str, colors: list[str], name: str, spc: str = ' ') -> None: def patch_color_list(path: str, colors: List[str], name: str, spc: str = ' ') -> None:
with open(path, 'r+') as f: with open(path, 'r+') as f:
raw = f.read() raw = f.read()
colors = sorted(colors) colors = sorted(colors)
@@ -39,8 +40,9 @@ def patch_color_list(path: str, colors: list[str], name: str, spc: str = ' ')
subprocess.check_call(['gofmt', '-w', path]) subprocess.check_call(['gofmt', '-w', path])
def main(args: list[str]=sys.argv) -> None: def main(args: List[str]=sys.argv) -> None:
from kitty.options.definition import definition from kitty.options.definition import definition
write_output('kitty', definition)
nullable_colors = [] nullable_colors = []
all_colors = [] all_colors = []
for opt in definition.iter_all_options(): for opt in definition.iter_all_options():
@@ -50,10 +52,9 @@ def main(args: list[str]=sys.argv) -> None:
all_colors.append(opt.name) all_colors.append(opt.name)
elif opt.parser_func.__name__ in ('to_color', 'titlebar_color', 'macos_titlebar_color'): elif opt.parser_func.__name__ in ('to_color', 'titlebar_color', 'macos_titlebar_color'):
all_colors.append(opt.name) all_colors.append(opt.name)
patch_color_list('kitty/rc/set_colors.py', nullable_colors, 'NULLABLE')
patch_color_list('tools/cmd/at/set_colors.go', nullable_colors, 'NULLABLE') patch_color_list('tools/cmd/at/set_colors.go', nullable_colors, 'NULLABLE')
patch_color_list('tools/themes/collection.go', all_colors, 'ALL') patch_color_list('tools/themes/collection.go', all_colors, 'ALL')
nc = ',\n '.join(f'{x!r}' for x in nullable_colors)
write_output('kitty', definition, f'\nnullable_colors = frozenset({{\n {nc}\n}})\n')
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -4,6 +4,7 @@
import os import os
import subprocess import subprocess
import sys import sys
from typing import List
if __name__ == '__main__' and not __package__: if __name__ == '__main__' and not __package__:
import __main__ import __main__
@@ -57,7 +58,7 @@ grabbing grabbing grabbing,closedhand,dnd-none grabbing
''' '''
def main(args: list[str]=sys.argv) -> None: def main(args: List[str]=sys.argv) -> None:
glfw_enum = [] glfw_enum = []
css_names = [] css_names = []
glfw_xc_map = {} glfw_xc_map = {}
@@ -118,7 +119,7 @@ def main(args: list[str]=sys.argv) -> None:
patch_file('glfw/x11_window.c', 'glfw to xc mapping', '\n'.join(f' {x}' for x in glfw_xfont_map)) patch_file('glfw/x11_window.c', 'glfw to xc mapping', '\n'.join(f' {x}' for x in glfw_xfont_map))
patch_file('kitty/data-types.h', 'mouse shapes', '\n'.join(f' {x},' for x in enum_to_glfw_map)) patch_file('kitty/data-types.h', 'mouse shapes', '\n'.join(f' {x},' for x in enum_to_glfw_map))
patch_file( patch_file(
'kitty/options/utils.py', 'pointer shape names', '\n'.join(f' {x!r},' for x in kitty_to_enum_map), 'kitty/options/definition.py', 'pointer shape names', '\n'.join(f' {x!r},' for x in kitty_to_enum_map),
start_marker='# ', end_marker='', start_marker='# ', end_marker='',
) )
patch_file('kitty/options/to-c.h', 'pointer shapes', '\n'.join( patch_file('kitty/options/to-c.h', 'pointer shapes', '\n'.join(

View File

@@ -12,15 +12,20 @@ import struct
import subprocess import subprocess
import sys import sys
import tarfile import tarfile
from collections.abc import Iterator, Sequence
from contextlib import contextmanager, suppress from contextlib import contextmanager, suppress
from functools import lru_cache from functools import lru_cache
from itertools import chain from itertools import chain
from typing import ( from typing import (
Any, Any,
BinaryIO, BinaryIO,
Dict,
Iterator,
List,
Optional, Optional,
Sequence,
Set,
TextIO, TextIO,
Tuple,
Union, Union,
) )
@@ -37,7 +42,6 @@ from kitty.cli import (
) )
from kitty.conf.generate import gen_go_code from kitty.conf.generate import gen_go_code
from kitty.conf.types import Definition from kitty.conf.types import Definition
from kitty.config import commented_out_default_config
from kitty.guess_mime_type import known_extensions, text_mimes from kitty.guess_mime_type import known_extensions, text_mimes
from kitty.key_encoding import config_mod_map from kitty.key_encoding import config_mod_map
from kitty.key_names import character_key_name_aliases, functional_key_name_aliases from kitty.key_names import character_key_name_aliases, functional_key_name_aliases
@@ -52,7 +56,7 @@ if __name__ == '__main__' and not __package__:
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
changed: list[str] = [] changed: List[str] = []
def newer(dest: str, *sources: str) -> bool: def newer(dest: str, *sources: str) -> bool:
@@ -70,7 +74,7 @@ def newer(dest: str, *sources: str) -> bool:
# Utils {{{ # Utils {{{
def serialize_go_dict(x: Union[dict[str, int], dict[int, str], dict[int, int], dict[str, str]]) -> str: def serialize_go_dict(x: Union[Dict[str, int], Dict[int, str], Dict[int, int], Dict[str, str]]) -> str:
ans = [] ans = []
def s(x: Union[int, str]) -> str: def s(x: Union[int, str]) -> str:
@@ -179,32 +183,6 @@ def stringify() -> None:
stringify_file(path) stringify_file(path)
# }}} # }}}
# {{{ Bitfields
def make_bitfields() -> None:
from kitty.fast_data_types import SCALE_BITS, SUBSCALE_BITS, WIDTH_BITS
from .bitfields import make_bitfield
def mb(*args: str) -> None:
output_path, ans = make_bitfield(*args)
with replace_if_needed(output_path) as buf:
print(ans, file=buf)
mb(
'tools/vt', 'CellAttrs',
'decoration 3', 'bold 1', 'italic 1', 'reverse 1', 'strike 1', 'dim 1', 'hyperlink_id 16',
)
mb('tools/vt', 'Ch', 'is_idx 1', 'ch_or_idx 31')
mb(
'tools/vt', 'MultiCell',
'is_multicell 1', 'natural_width 1', f'scale {SCALE_BITS}', f'subscale_n {SUBSCALE_BITS}', f'subscale_d {SUBSCALE_BITS}',
f'width {WIDTH_BITS}', f'x {WIDTH_BITS + SCALE_BITS + 1}', f'y {SCALE_BITS + 1}', 'vertical_align 3',
)
mb('tools/vt', 'CellColor', 'is_idx 1', 'red 8', 'green 8', 'blue 8')
mb('tools/vt', 'LineAttrs', 'prompt_kind 2',)
# }}}
# Completions {{{ # Completions {{{
@lru_cache @lru_cache
@@ -214,7 +192,7 @@ def kitten_cli_docs(kitten: str) -> Any:
@lru_cache @lru_cache
def go_options_for_kitten(kitten: str) -> tuple[Sequence[GoOption], Optional[CompletionSpec]]: def go_options_for_kitten(kitten: str) -> Tuple[Sequence[GoOption], Optional[CompletionSpec]]:
kcd = kitten_cli_docs(kitten) kcd = kitten_cli_docs(kitten)
if kcd: if kcd:
ospec = kcd['options'] ospec = kcd['options']
@@ -319,7 +297,7 @@ def generate_completions_for_kitty() -> None:
# rc command wrappers {{{ # rc command wrappers {{{
json_field_types: dict[str, str] = { json_field_types: Dict[str, str] = {
'bool': 'bool', 'str': 'escaped_string', 'list.str': '[]escaped_string', 'dict.str': 'map[escaped_string]escaped_string', 'float': 'float64', 'int': 'int', 'bool': 'bool', 'str': 'escaped_string', 'list.str': '[]escaped_string', 'dict.str': 'map[escaped_string]escaped_string', 'float': 'float64', 'int': 'int',
'scroll_amount': 'any', 'spacing': 'any', 'colors': 'any', 'scroll_amount': 'any', 'spacing': 'any', 'colors': 'any',
} }
@@ -341,16 +319,10 @@ def go_field_type(json_field_type: str) -> str:
class JSONField: class JSONField:
def __init__(self, line: str, field_to_option_map: dict[str, str], option_map: dict[str, GoOption]) -> None: def __init__(self, line: str) -> None:
field_def = line.split(':', 1)[0] field_def = line.split(':', 1)[0]
self.required = False self.required = False
self.field, self.field_type = field_def.split('/', 1) self.field, self.field_type = field_def.split('/', 1)
self.go_option_name = field_to_option_map.get(self.field, self.field)
self.go_option_name = ''.join(x.capitalize() for x in self.go_option_name.split('_'))
self.omitempty = True
if fo := option_map.get(self.go_option_name):
if fo.type in ('int', 'float') and float(fo.default or 0) != 0:
self.omitempty = False
self.field_type, self.special_parser = self.field_type.partition('=')[::2] self.field_type, self.special_parser = self.field_type.partition('=')[::2]
if self.field.endswith('+'): if self.field.endswith('+'):
self.required = True self.required = True
@@ -358,44 +330,45 @@ class JSONField:
self.struct_field_name = self.field[0].upper() + self.field[1:] self.struct_field_name = self.field[0].upper() + self.field[1:]
def go_declaration(self) -> str: def go_declaration(self) -> str:
omitempty = ',omitempty' if self.omitempty else '' return self.struct_field_name + ' ' + go_field_type(self.field_type) + f'`json:"{self.field},omitempty"`'
return self.struct_field_name + ' ' + go_field_type(self.field_type) + f'`json:"{self.field}{omitempty}"`'
def go_code_for_remote_command(name: str, cmd: RemoteCommand, template: str) -> str: def go_code_for_remote_command(name: str, cmd: RemoteCommand, template: str) -> str:
template = '\n' + template[len('//go:build exclude'):] template = '\n' + template[len('//go:build exclude'):]
af: list[str] = [] af: List[str] = []
a = af.append a = af.append
af.extend(cmd.args.as_go_completion_code('ans')) af.extend(cmd.args.as_go_completion_code('ans'))
od: list[str] = [] od: List[str] = []
option_map: dict[str, GoOption] = {} option_map: Dict[str, GoOption] = {}
for o in rc_command_options(name): for o in rc_command_options(name):
option_map[o.go_var_name] = o option_map[o.go_var_name] = o
a(o.as_option('ans')) a(o.as_option('ans'))
if o.go_var_name in ('NoResponse', 'ResponseTimeout'): if o.go_var_name in ('NoResponse', 'ResponseTimeout'):
continue continue
od.append(o.struct_declaration()) od.append(o.struct_declaration())
jd: list[str] = [] jd: List[str] = []
json_fields = [] json_fields = []
field_types: dict[str, str] = {} field_types: Dict[str, str] = {}
for line in cmd.protocol_spec.splitlines(): for line in cmd.protocol_spec.splitlines():
line = line.strip() line = line.strip()
if ':' not in line: if ':' not in line:
continue continue
f = JSONField(line, cmd.field_to_option_map or {}, option_map) f = JSONField(line)
json_fields.append(f) json_fields.append(f)
field_types[f.field] = f.field_type field_types[f.field] = f.field_type
jd.append(f.go_declaration()) jd.append(f.go_declaration())
jc: list[str] = [] jc: List[str] = []
handled_fields: set[str] = set() handled_fields: Set[str] = set()
jc.extend(cmd.args.as_go_code(name, field_types, handled_fields)) jc.extend(cmd.args.as_go_code(name, field_types, handled_fields))
unhandled = {} unhandled = {}
used_options = set() used_options = set()
for field in json_fields: for field in json_fields:
if field.go_option_name in option_map: oq = (cmd.field_to_option_map or {}).get(field.field, field.field)
o = option_map[field.go_option_name] oq = ''.join(x.capitalize() for x in oq.split('_'))
used_options.add(field.go_option_name) if oq in option_map:
o = option_map[oq]
used_options.add(oq)
optstring = f'options_{name}.{o.go_var_name}' optstring = f'options_{name}.{o.go_var_name}'
if field.special_parser: if field.special_parser:
optstring = f'{field.special_parser}({optstring})' optstring = f'{field.special_parser}({optstring})'
@@ -453,7 +426,7 @@ def go_code_for_remote_command(name: str, cmd: RemoteCommand, template: str) ->
# kittens {{{ # kittens {{{
@lru_cache @lru_cache
def wrapped_kittens() -> tuple[str, ...]: def wrapped_kittens() -> Tuple[str, ...]:
with open('shell-integration/ssh/kitty') as f: with open('shell-integration/ssh/kitty') as f:
for line in f: for line in f:
if line.startswith(' wrapped_kittens="'): if line.startswith(' wrapped_kittens="'):
@@ -488,20 +461,9 @@ def generate_extra_cli_parser(name: str, spec: str) -> None:
print('}') print('}')
def kittens_needing_cli_parsers() -> Iterator[str]:
for d in os.scandir('kittens'):
if not d.is_dir(follow_symlinks=False):
continue
if os.path.exists(os.path.join(d.path, 'main.py')) and os.path.exists(os.path.join(d.path, 'main.go')):
with open(os.path.join(d.path, 'main.py')) as f:
raw = f.read()
if 'options' in raw:
yield d.name
def kitten_clis() -> None: def kitten_clis() -> None:
from kittens.runner import get_kitten_conf_docs, get_kitten_extra_cli_parsers from kittens.runner import get_kitten_conf_docs, get_kitten_extra_cli_parsers
for kitten in kittens_needing_cli_parsers(): for kitten in wrapped_kittens() + ('pager',):
defn = get_kitten_conf_docs(kitten) defn = get_kitten_conf_docs(kitten)
if defn is not None: if defn is not None:
generate_conf_parser(kitten, defn) generate_conf_parser(kitten, defn)
@@ -598,7 +560,7 @@ SelectionBg: "{selbg}",
''' '''
def load_ref_map() -> dict[str, dict[str, str]]: def load_ref_map() -> Dict[str, Dict[str, str]]:
with open('kitty/docs_ref_map_generated.h') as f: with open('kitty/docs_ref_map_generated.h') as f:
raw = f.read() raw = f.read()
raw = raw.split('{', 1)[1].split('}', 1)[0] raw = raw.split('{', 1)[1].split('}', 1)[0]
@@ -608,13 +570,10 @@ def load_ref_map() -> dict[str, dict[str, str]]:
def generate_constants() -> str: def generate_constants() -> str:
from kittens.hints.main import DEFAULT_REGEX from kittens.hints.main import DEFAULT_REGEX
from kittens.query_terminal.main import all_queries
from kitty.colors import ThemeFile
from kitty.config import option_names_for_completion from kitty.config import option_names_for_completion
from kitty.fast_data_types import FILE_TRANSFER_CODE from kitty.fast_data_types import FILE_TRANSFER_CODE
from kitty.options.utils import allowed_shell_integration_values, url_style_map from kitty.options.utils import allowed_shell_integration_values
del sys.modules['kittens.hints.main'] del sys.modules['kittens.hints.main']
del sys.modules['kittens.query_terminal.main']
ref_map = load_ref_map() ref_map = load_ref_map()
with open('kitty/data-types.h') as dt: with open('kitty/data-types.h') as dt:
m = re.search(r'^#define IMAGE_PLACEHOLDER_CHAR (\S+)', dt.read(), flags=re.M) m = re.search(r'^#define IMAGE_PLACEHOLDER_CHAR (\S+)', dt.read(), flags=re.M)
@@ -623,8 +582,6 @@ def generate_constants() -> str:
dp = ", ".join(map(lambda x: f'"{serialize_as_go_string(x)}"', kc.default_pager_for_help)) dp = ", ".join(map(lambda x: f'"{serialize_as_go_string(x)}"', kc.default_pager_for_help))
url_prefixes = ','.join(f'"{x}"' for x in Options.url_prefixes) url_prefixes = ','.join(f'"{x}"' for x in Options.url_prefixes)
option_names = '`' + '\n'.join(option_names_for_completion()) + '`' option_names = '`' + '\n'.join(option_names_for_completion()) + '`'
url_style = {v:k for k, v in url_style_map.items()}[Options.url_style]
query_names = ', '.join(f'"{name}"' for name in all_queries)
return f'''\ return f'''\
package kitty package kitty
@@ -643,8 +600,6 @@ var IsStandaloneBuild string = ""
const HandleTermiosSignals = {Mode.HANDLE_TERMIOS_SIGNALS.value[0]} const HandleTermiosSignals = {Mode.HANDLE_TERMIOS_SIGNALS.value[0]}
const HintsDefaultRegex = `{DEFAULT_REGEX}` const HintsDefaultRegex = `{DEFAULT_REGEX}`
const DefaultTermName = `{Options.term}` const DefaultTermName = `{Options.term}`
const DefaultUrlStyle = `{url_style}`
const DefaultUrlColor = `{Options.url_color.as_sharp}`
var Version VersionType = VersionType{{Major: {kc.version.major}, Minor: {kc.version.minor}, Patch: {kc.version.patch},}} var Version VersionType = VersionType{{Major: {kc.version.major}, Minor: {kc.version.minor}, Patch: {kc.version.patch},}}
var DefaultPager []string = []string{{ {dp} }} var DefaultPager []string = []string{{ {dp} }}
var FunctionalKeyNameAliases = map[string]string{serialize_go_dict(functional_key_name_aliases)} var FunctionalKeyNameAliases = map[string]string{serialize_go_dict(functional_key_name_aliases)}
@@ -653,8 +608,6 @@ var ConfigModMap = map[string]uint16{serialize_go_dict(config_mod_map)}
var RefMap = map[string]string{serialize_go_dict(ref_map['ref'])} var RefMap = map[string]string{serialize_go_dict(ref_map['ref'])}
var DocTitleMap = map[string]string{serialize_go_dict(ref_map['doc'])} var DocTitleMap = map[string]string{serialize_go_dict(ref_map['doc'])}
var AllowedShellIntegrationValues = []string{{ {str(sorted(allowed_shell_integration_values))[1:-1].replace("'", '"')} }} var AllowedShellIntegrationValues = []string{{ {str(sorted(allowed_shell_integration_values))[1:-1].replace("'", '"')} }}
var QueryNames = []string{{ {query_names} }}
var CommentedOutDefaultConfig = "{serialize_as_go_string(commented_out_default_config())}"
var KittyConfigDefaults = struct {{ var KittyConfigDefaults = struct {{
Term, Shell_integration, Select_by_word_characters, Url_excluded_characters, Shell string Term, Shell_integration, Select_by_word_characters, Url_excluded_characters, Shell string
Wheel_scroll_multiplier int Wheel_scroll_multiplier int
@@ -665,9 +618,6 @@ Select_by_word_characters: `{Options.select_by_word_characters}`, Wheel_scroll_m
Shell: "{Options.shell}", Url_excluded_characters: "{Options.url_excluded_characters}", Shell: "{Options.shell}", Url_excluded_characters: "{Options.url_excluded_characters}",
}} }}
const OptionNames = {option_names} const OptionNames = {option_names}
const DarkThemeFileName = "{ThemeFile.dark.value}"
const LightThemeFileName = "{ThemeFile.light.value}"
const NoPreferenceThemeFileName = "{ThemeFile.no_preference.value}"
''' # }}} ''' # }}}
@@ -683,7 +633,7 @@ def replace_if_needed(path: str, show_diff: bool = False) -> Iterator[io.StringI
finally: finally:
sys.stdout = origb sys.stdout = origb
orig = '' orig = ''
with suppress(FileNotFoundError), open(path) as f: with suppress(FileNotFoundError), open(path, 'r') as f:
orig = f.read() orig = f.read()
new = buf.getvalue() new = buf.getvalue()
new = f'// Code generated by {os.path.basename(__file__)}; DO NOT EDIT.\n\n' + new new = f'// Code generated by {os.path.basename(__file__)}; DO NOT EDIT.\n\n' + new
@@ -699,7 +649,7 @@ def replace_if_needed(path: str, show_diff: bool = False) -> Iterator[io.StringI
@lru_cache(maxsize=256) @lru_cache(maxsize=256)
def rc_command_options(name: str) -> tuple[GoOption, ...]: def rc_command_options(name: str) -> Tuple[GoOption, ...]:
cmd = command_for_name(name) cmd = command_for_name(name)
return tuple(go_options_for_seq(parse_option_spec(cmd.options_spec or '\n\n')[0])) return tuple(go_options_for_seq(parse_option_spec(cmd.options_spec or '\n\n')[0]))
@@ -909,7 +859,7 @@ def start_simdgen() -> 'subprocess.Popen[bytes]':
return subprocess.Popen(['go', 'run', 'generate.go'], cwd='tools/simdstring', stdout=subprocess.PIPE, stderr=subprocess.PIPE) return subprocess.Popen(['go', 'run', 'generate.go'], cwd='tools/simdstring', stdout=subprocess.PIPE, stderr=subprocess.PIPE)
def main(args: list[str]=sys.argv) -> None: def main(args: List[str]=sys.argv) -> None:
simdgen_process = start_simdgen() simdgen_process = start_simdgen()
with replace_if_needed('constants_generated.go') as f: with replace_if_needed('constants_generated.go') as f:
f.write(generate_constants()) f.write(generate_constants())
@@ -932,7 +882,6 @@ def main(args: list[str]=sys.argv) -> None:
update_at_commands() update_at_commands()
kitten_clis() kitten_clis()
stringify() stringify()
make_bitfields()
print(json.dumps(changed, indent=2)) print(json.dumps(changed, indent=2))
stdout, stderr = simdgen_process.communicate() stdout, stderr = simdgen_process.communicate()
if simdgen_process.wait() != 0: if simdgen_process.wait() != 0:

View File

@@ -6,7 +6,7 @@ import string
import subprocess import subprocess
import sys import sys
from pprint import pformat from pprint import pformat
from typing import Any, Union from typing import Any, Dict, List, Union
if __name__ == '__main__' and not __package__: if __name__ == '__main__' and not __package__:
import __main__ import __main__
@@ -194,11 +194,11 @@ macos_ansi_key_codes = { # {{{
0x31: ord(' '), 0x31: ord(' '),
} # }}} } # }}}
functional_key_names: list[str] = [] functional_key_names: List[str] = []
name_to_code: dict[str, int] = {} name_to_code: Dict[str, int] = {}
name_to_xkb: dict[str, str] = {} name_to_xkb: Dict[str, str] = {}
name_to_vk: dict[str, int] = {} name_to_vk: Dict[str, int] = {}
name_to_macu: dict[str, str] = {} name_to_macu: Dict[str, str] = {}
start_code = 0xe000 start_code = 0xe000
for line in functional_key_defs.splitlines(): for line in functional_key_defs.splitlines():
line = line.strip() line = line.strip()
@@ -254,11 +254,11 @@ def patch_file(path: str, what: str, text: str, start_marker: str = '/* ', end_m
subprocess.check_call(['go', 'fmt', path]) subprocess.check_call(['go', 'fmt', path])
def serialize_dict(x: dict[Any, Any]) -> str: def serialize_dict(x: Dict[Any, Any]) -> str:
return pformat(x, indent=4).replace('{', '{\n ', 1) return pformat(x, indent=4).replace('{', '{\n ', 1)
def serialize_go_dict(x: Union[dict[str, int], dict[int, str], dict[int, int]]) -> str: def serialize_go_dict(x: Union[Dict[str, int], Dict[int, str], Dict[int, int]]) -> str:
ans = [] ans = []
def s(x: Union[int, str]) -> str: def s(x: Union[int, str]) -> str:
@@ -332,7 +332,7 @@ def generate_functional_table() -> None:
patch_file('kitty/key_encoding.c', 'special numbers', '\n'.join(enc_lines)) patch_file('kitty/key_encoding.c', 'special numbers', '\n'.join(enc_lines))
code_to_name = {v: k.upper() for k, v in name_to_code.items()} code_to_name = {v: k.upper() for k, v in name_to_code.items()}
csi_map = {v: name_to_code[k] for k, v in functional_encoding_overrides.items()} csi_map = {v: name_to_code[k] for k, v in functional_encoding_overrides.items()}
letter_trailer_codes: dict[str, int] = { letter_trailer_codes: Dict[str, int] = {
v: functional_encoding_overrides.get(k, name_to_code.get(k, 0)) v: functional_encoding_overrides.get(k, name_to_code.get(k, 0))
for k, v in different_trailer_functionals.items() if v in 'ABCDEHFPQRSZ'} for k, v in different_trailer_functionals.items() if v in 'ABCDEHFPQRSZ'}
text = f'functional_key_number_to_name_map = {serialize_dict(code_to_name)}' text = f'functional_key_number_to_name_map = {serialize_dict(code_to_name)}'
@@ -377,7 +377,7 @@ def generate_legacy_text_key_maps() -> None:
patch_file('kitty_tests/keys.py', 'legacy letter tests', '\n'.join(tests), start_marker='# ', end_marker='') patch_file('kitty_tests/keys.py', 'legacy letter tests', '\n'.join(tests), start_marker='# ', end_marker='')
def chunks(lst: list[Any], n: int) -> Any: def chunks(lst: List[Any], n: int) -> Any:
"""Yield successive n-sized chunks from lst.""" """Yield successive n-sized chunks from lst."""
for i in range(0, len(lst), n): for i in range(0, len(lst), n):
yield lst[i:i + n] yield lst[i:i + n]
@@ -427,7 +427,7 @@ def generate_macos_mapping() -> None:
patch_file('glfw/cocoa_window.m', 'functional to macu', '\n'.join(lines)) patch_file('glfw/cocoa_window.m', 'functional to macu', '\n'.join(lines))
def main(args: list[str]=sys.argv) -> None: def main(args: List[str]=sys.argv) -> None:
generate_glfw_header() generate_glfw_header()
generate_xkb_mapping() generate_xkb_mapping()
generate_functional_table() generate_functional_table()

View File

@@ -1,8 +1,10 @@
#!/usr/bin/env python #!/usr/bin/env python
# vim:fileencoding=utf-8
import os import os
import sys import sys
from functools import lru_cache from functools import lru_cache
from typing import List
if __name__ == '__main__' and not __package__: if __name__ == '__main__' and not __package__:
import __main__ import __main__
@@ -18,12 +20,12 @@ def to_linear(a: float) -> float:
@lru_cache @lru_cache
def generate_srgb_lut(line_prefix: str = ' ') -> list[str]: def generate_srgb_lut(line_prefix: str = ' ') -> List[str]:
values: list[str] = [] values: List[str] = []
lines: list[str] = [] lines: List[str] = []
for i in range(256): for i in range(256):
values.append(f'{to_linear(i / 255.0):1.5f}f') values.append('{:1.5f}f'.format(to_linear(i / 255.0)))
for i in range(16): for i in range(16):
lines.append(line_prefix + ', '.join(values[i * 16:(i + 1) * 16]) + ',') lines.append(line_prefix + ', '.join(values[i * 16:(i + 1) * 16]) + ',')
@@ -33,7 +35,7 @@ def generate_srgb_lut(line_prefix: str = ' ') -> list[str]:
def generate_srgb_gamma(declaration: str = 'static const GLfloat srgb_lut[256] = {', close: str = '};') -> str: def generate_srgb_gamma(declaration: str = 'static const GLfloat srgb_lut[256] = {', close: str = '};') -> str:
lines: list[str] = [] lines: List[str] = []
a = lines.append a = lines.append
a('// Generated by gen-srgb-lut.py DO NOT edit') a('// Generated by gen-srgb-lut.py DO NOT edit')
@@ -45,7 +47,7 @@ def generate_srgb_gamma(declaration: str = 'static const GLfloat srgb_lut[256] =
return "\n".join(lines) return "\n".join(lines)
def main(args: list[str]=sys.argv) -> None: def main(args: List[str]=sys.argv) -> None:
c = generate_srgb_gamma() c = generate_srgb_gamma()
with open(os.path.join('kitty', 'srgb_gamma.h'), 'w') as f: with open(os.path.join('kitty', 'srgb_gamma.h'), 'w') as f:
f.write(f'{c}\n') f.write(f'{c}\n')

File diff suppressed because it is too large Load Diff

View File

@@ -1,160 +0,0 @@
/*
* cocoa_displaylink.m
* Copyright (C) 2024 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
// CVDisplayLink is deprecated replace with CADisplayLink via [NSScreen displayLink] once base macOS version is 14
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#include "internal.h"
#include <CoreVideo/CVDisplayLink.h>
#define DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL s_to_monotonic_t(30ll)
typedef struct _GLFWDisplayLinkNS
{
CVDisplayLinkRef displayLink;
CGDirectDisplayID displayID;
monotonic_t lastRenderFrameRequestedAt, first_unserviced_render_frame_request_at;
} _GLFWDisplayLinkNS;
static struct {
_GLFWDisplayLinkNS entries[256];
size_t count;
} displayLinks = {0};
static CGDirectDisplayID
displayIDForWindow(_GLFWwindow *w) {
NSWindow *nw = w->ns.object;
NSDictionary *dict = [nw.screen deviceDescription];
NSNumber *displayIDns = dict[@"NSScreenNumber"];
if (displayIDns) return [displayIDns unsignedIntValue];
return (CGDirectDisplayID)-1;
}
void
_glfwClearDisplayLinks(void) {
for (size_t i = 0; i < displayLinks.count; i++) {
if (displayLinks.entries[i].displayLink) {
CVDisplayLinkStop(displayLinks.entries[i].displayLink);
CVDisplayLinkRelease(displayLinks.entries[i].displayLink);
}
}
memset(displayLinks.entries, 0, sizeof(_GLFWDisplayLinkNS) * displayLinks.count);
displayLinks.count = 0;
}
static CVReturn
displayLinkCallback(
CVDisplayLinkRef displayLink UNUSED,
const CVTimeStamp* now UNUSED, const CVTimeStamp* outputTime UNUSED,
CVOptionFlags flagsIn UNUSED, CVOptionFlags* flagsOut UNUSED, void* userInfo) {
CGDirectDisplayID displayID = (uintptr_t)userInfo;
NSNumber *arg = [NSNumber numberWithUnsignedInt:displayID];
[NSApp performSelectorOnMainThread:@selector(render_frame_received:) withObject:arg waitUntilDone:NO];
[arg release];
return kCVReturnSuccess;
}
static void
_glfw_create_cv_display_link(_GLFWDisplayLinkNS *entry) {
CVDisplayLinkCreateWithCGDisplay(entry->displayID, &entry->displayLink);
CVDisplayLinkSetOutputCallback(entry->displayLink, &displayLinkCallback, (void*)(uintptr_t)entry->displayID);
}
unsigned
_glfwCreateDisplayLink(CGDirectDisplayID displayID) {
if (displayLinks.count >= arraysz(displayLinks.entries) - 1) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Too many monitors cannot create display link");
return displayLinks.count;
}
for (unsigned i = 0; i < displayLinks.count; i++) {
// already created in this run
if (displayLinks.entries[i].displayID == displayID) return i;
}
_GLFWDisplayLinkNS *entry = &displayLinks.entries[displayLinks.count++];
memset(entry, 0, sizeof(_GLFWDisplayLinkNS));
entry->displayID = displayID;
_glfw_create_cv_display_link(entry);
return displayLinks.count - 1;
}
static unsigned long long display_link_shutdown_timer = 0;
static void
_glfwShutdownCVDisplayLink(unsigned long long timer_id UNUSED, void *user_data UNUSED) {
display_link_shutdown_timer = 0;
for (size_t i = 0; i < displayLinks.count; i++) {
_GLFWDisplayLinkNS *dl = &displayLinks.entries[i];
if (dl->displayLink) CVDisplayLinkStop(dl->displayLink);
dl->lastRenderFrameRequestedAt = 0;
dl->first_unserviced_render_frame_request_at = 0;
}
}
void
_glfwRequestRenderFrame(_GLFWwindow *w) {
CGDirectDisplayID displayID = displayIDForWindow(w);
if (display_link_shutdown_timer) {
_glfwPlatformUpdateTimer(display_link_shutdown_timer, DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL, true);
} else {
display_link_shutdown_timer = _glfwPlatformAddTimer(DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL, false, _glfwShutdownCVDisplayLink, NULL, NULL);
}
monotonic_t now = glfwGetTime();
bool found_display_link = false;
_GLFWDisplayLinkNS *dl = NULL;
for (size_t i = 0; i < displayLinks.count; i++) {
dl = &displayLinks.entries[i];
if (dl->displayID == displayID) {
found_display_link = true;
dl->lastRenderFrameRequestedAt = now;
if (!dl->first_unserviced_render_frame_request_at) dl->first_unserviced_render_frame_request_at = now;
if (!CVDisplayLinkIsRunning(dl->displayLink)) CVDisplayLinkStart(dl->displayLink);
else if (now - dl->first_unserviced_render_frame_request_at > s_to_monotonic_t(1ll)) {
// display link is stuck need to recreate it because Apple can't even
// get a simple timer right
CVDisplayLinkRelease(dl->displayLink); dl->displayLink = nil;
dl->first_unserviced_render_frame_request_at = now;
_glfw_create_cv_display_link(dl);
_glfwInputError(GLFW_PLATFORM_ERROR,
"CVDisplayLink stuck possibly because of sleep/screensaver + Apple's incompetence, recreating.");
if (!CVDisplayLinkIsRunning(dl->displayLink)) CVDisplayLinkStart(dl->displayLink);
}
} else if (dl->displayLink && dl->lastRenderFrameRequestedAt && now - dl->lastRenderFrameRequestedAt >= DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL) {
CVDisplayLinkStop(dl->displayLink);
dl->lastRenderFrameRequestedAt = 0;
dl->first_unserviced_render_frame_request_at = 0;
}
}
if (!found_display_link) {
unsigned idx = _glfwCreateDisplayLink(displayID);
if (idx < displayLinks.count) {
dl = &displayLinks.entries[idx];
dl->lastRenderFrameRequestedAt = now;
dl->first_unserviced_render_frame_request_at = now;
if (!CVDisplayLinkIsRunning(dl->displayLink)) CVDisplayLinkStart(dl->displayLink);
}
}
}
void
_glfwDispatchRenderFrame(CGDirectDisplayID displayID) {
_GLFWwindow *w = _glfw.windowListHead;
while (w) {
if (w->ns.renderFrameRequested && displayID == displayIDForWindow(w)) {
w->ns.renderFrameRequested = false;
w->ns.renderFrameCallback((GLFWwindow*)w);
}
w = w->next;
}
for (size_t i = 0; i < displayLinks.count; i++) {
_GLFWDisplayLinkNS *dl = &displayLinks.entries[i];
if (dl->displayID == displayID) {
dl->first_unserviced_render_frame_request_at = 0;
}
}
}

View File

@@ -579,7 +579,7 @@ is_shiftable_shortcut(int scv) {
return scv == kSHKMoveFocusToActiveOrNextWindow || scv == kSHKMoveFocusToNextWindow; return scv == kSHKMoveFocusToActiveOrNextWindow || scv == kSHKMoveFocusToNextWindow;
} }
#define USEFUL_MODS(x) (x & (NSEventModifierFlagShift | NSEventModifierFlagOption | NSEventModifierFlagCommand | NSEventModifierFlagControl | NSEventModifierFlagFunction)) #define USEFUL_MODS(x) (x & (NSEventModifierFlagShift | NSEventModifierFlagOption | NSEventModifierFlagCommand | NSEventModifierFlagControl))
static void static void
build_global_shortcuts_lookup(void) { build_global_shortcuts_lookup(void) {
@@ -791,11 +791,10 @@ GLFWAPI GLFWapplicationwillfinishlaunchingfun glfwSetApplicationWillFinishLaunch
return previous; return previous;
} }
int _glfwPlatformInit(bool *supports_window_occlusion) int _glfwPlatformInit(void)
{ {
@autoreleasepool { @autoreleasepool {
*supports_window_occlusion = true;
_glfw.ns.helper = [[GLFWHelper alloc] init]; _glfw.ns.helper = [[GLFWHelper alloc] init];
[NSThread detachNewThreadSelector:@selector(doNothing:) [NSThread detachNewThreadSelector:@selector(doNothing:)
@@ -1142,5 +1141,3 @@ void _glfwPlatformUpdateTimer(unsigned long long timer_id, monotonic_t interval,
} }
} }
} }
void _glfwPlatformInputColorScheme(GLFWColorScheme appearance UNUSED) { }

View File

@@ -35,6 +35,7 @@
#include <IOKit/graphics/IOGraphicsLib.h> #include <IOKit/graphics/IOGraphicsLib.h>
#include <CoreVideo/CVBase.h> #include <CoreVideo/CVBase.h>
#include <CoreVideo/CVDisplayLink.h>
#include <ApplicationServices/ApplicationServices.h> #include <ApplicationServices/ApplicationServices.h>
@@ -322,7 +323,54 @@ static double getFallbackRefreshRate(CGDirectDisplayID displayID)
////// GLFW internal API ////// ////// GLFW internal API //////
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
void _glfwClearDisplayLinks(void) {
for (size_t i = 0; i < _glfw.ns.displayLinks.count; i++) {
if (_glfw.ns.displayLinks.entries[i].displayLink) {
CVDisplayLinkStop(_glfw.ns.displayLinks.entries[i].displayLink);
CVDisplayLinkRelease(_glfw.ns.displayLinks.entries[i].displayLink);
}
}
memset(_glfw.ns.displayLinks.entries, 0, sizeof(_GLFWDisplayLinkNS) * _glfw.ns.displayLinks.count);
_glfw.ns.displayLinks.count = 0;
}
static CVReturn displayLinkCallback(
CVDisplayLinkRef displayLink UNUSED,
const CVTimeStamp* now UNUSED, const CVTimeStamp* outputTime UNUSED,
CVOptionFlags flagsIn UNUSED, CVOptionFlags* flagsOut UNUSED, void* userInfo)
{
CGDirectDisplayID displayID = (uintptr_t)userInfo;
NSNumber *arg = [NSNumber numberWithUnsignedInt:displayID];
[NSApp performSelectorOnMainThread:@selector(render_frame_received:) withObject:arg waitUntilDone:NO];
[arg release];
return kCVReturnSuccess;
}
void
_glfw_create_cv_display_link(_GLFWDisplayLinkNS *entry) {
CVDisplayLinkCreateWithCGDisplay(entry->displayID, &entry->displayLink);
CVDisplayLinkSetOutputCallback(entry->displayLink, &displayLinkCallback, (void*)(uintptr_t)entry->displayID);
}
_GLFWDisplayLinkNS*
_glfw_create_display_link(CGDirectDisplayID displayID) {
if (_glfw.ns.displayLinks.count >= arraysz(_glfw.ns.displayLinks.entries) - 1) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Too many monitors cannot create display link");
return NULL;
}
for (size_t i = 0; i < _glfw.ns.displayLinks.count; i++) {
// already created in this run
if (_glfw.ns.displayLinks.entries[i].displayID == displayID) return _glfw.ns.displayLinks.entries + i;
}
_GLFWDisplayLinkNS *entry = &_glfw.ns.displayLinks.entries[_glfw.ns.displayLinks.count++];
memset(entry, 0, sizeof(_GLFWDisplayLinkNS));
entry->displayID = displayID;
_glfw_create_cv_display_link(entry);
return entry;
}
// Poll for changes in the set of connected monitors // Poll for changes in the set of connected monitors
//
void _glfwPollMonitorsNS(void) void _glfwPollMonitorsNS(void)
{ {
uint32_t displayCount; uint32_t displayCount;
@@ -378,7 +426,7 @@ void _glfwPollMonitorsNS(void)
{ {
disconnected[j]->ns.displayID = displays[i]; disconnected[j]->ns.displayID = displays[i];
disconnected[j]->ns.screen = screen; disconnected[j]->ns.screen = screen;
_glfwCreateDisplayLink(displays[i]); _glfw_create_display_link(displays[i]);
disconnected[j] = NULL; disconnected[j] = NULL;
break; break;
} }
@@ -399,7 +447,7 @@ void _glfwPollMonitorsNS(void)
monitor->ns.displayID = displays[i]; monitor->ns.displayID = displays[i];
monitor->ns.unitNumber = unitNumber; monitor->ns.unitNumber = unitNumber;
monitor->ns.screen = screen; monitor->ns.screen = screen;
_glfwCreateDisplayLink(monitor->ns.displayID); _glfw_create_display_link(monitor->ns.displayID);
free(name); free(name);
@@ -579,7 +627,6 @@ bool _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode *mode)
} }
*mode = vidmodeFromCGDisplayMode(native, monitor->ns.fallbackRefreshRate); *mode = vidmodeFromCGDisplayMode(native, monitor->ns.fallbackRefreshRate);
CGDisplayModeRelease(native); CGDisplayModeRelease(native);
return true;
} }
bool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp) bool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)

27
glfw/cocoa_platform.h vendored
View File

@@ -30,8 +30,10 @@
#include <Carbon/Carbon.h> #include <Carbon/Carbon.h>
#if defined(__OBJC__) #if defined(__OBJC__)
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#import <CoreVideo/CoreVideo.h>
#else #else
typedef void* id; typedef void* id;
typedef void* CVDisplayLinkRef;
#endif #endif
// NOTE: Many Cocoa enum values have been renamed and we need to build across // NOTE: Many Cocoa enum values have been renamed and we need to build across
@@ -158,6 +160,13 @@ typedef struct _GLFWwindowNS
GLFWcocoarenderframefun resizeCallback; GLFWcocoarenderframefun resizeCallback;
} _GLFWwindowNS; } _GLFWwindowNS;
typedef struct _GLFWDisplayLinkNS
{
CVDisplayLinkRef displayLink;
CGDirectDisplayID displayID;
monotonic_t lastRenderFrameRequestedAt, first_unserviced_render_frame_request_at;
} _GLFWDisplayLinkNS;
// Cocoa-specific global data // Cocoa-specific global data
// //
typedef struct _GLFWlibraryNS typedef struct _GLFWlibraryNS
@@ -175,7 +184,7 @@ typedef struct _GLFWlibraryNS
id nibObjects; id nibObjects;
char keyName[64]; char keyName[64];
char text[512]; char text[256];
CGPoint cascadePoint; CGPoint cascadePoint;
// Where to place the cursor when re-enabled // Where to place the cursor when re-enabled
double restoreCursorPosX, restoreCursorPosY; double restoreCursorPosX, restoreCursorPosY;
@@ -190,6 +199,10 @@ typedef struct _GLFWlibraryNS
CFStringRef kPropertyUnicodeKeyLayoutData; CFStringRef kPropertyUnicodeKeyLayoutData;
} tis; } tis;
struct {
_GLFWDisplayLinkNS entries[256];
size_t count;
} displayLinks;
// the callback to handle url open events // the callback to handle url open events
GLFWhandleurlopen url_open_callback; GLFWhandleurlopen url_open_callback;
@@ -231,16 +244,12 @@ float _glfwTransformYNS(float y);
void* _glfwLoadLocalVulkanLoaderNS(void); void* _glfwLoadLocalVulkanLoaderNS(void);
// display links
void _glfwClearDisplayLinks(void); void _glfwClearDisplayLinks(void);
void _glfwRestartDisplayLinks(void); void _glfwRestartDisplayLinks(void);
unsigned _glfwCreateDisplayLink(CGDirectDisplayID);
void _glfwDispatchRenderFrame(CGDirectDisplayID);
void _glfwRequestRenderFrame(_GLFWwindow *w);
// event loop
void _glfwDispatchTickCallback(void); void _glfwDispatchTickCallback(void);
void _glfwDispatchRenderFrame(CGDirectDisplayID);
void _glfwShutdownCVDisplayLink(unsigned long long, void*);
void _glfwCocoaPostEmptyEvent(void); void _glfwCocoaPostEmptyEvent(void);
void _glfw_create_cv_display_link(_GLFWDisplayLinkNS *entry);
_GLFWDisplayLinkNS* _glfw_create_display_link(CGDirectDisplayID);
uint32_t vk_to_unicode_key_with_current_layout(uint16_t keycode); uint32_t vk_to_unicode_key_with_current_layout(uint16_t keycode);

View File

@@ -36,7 +36,7 @@
#include <float.h> #include <float.h>
#include <string.h> #include <string.h>
#define debug debug_rendering #define debug(...) if (_glfw.hints.init.debugRendering) fprintf(stderr, __VA_ARGS__);
static const char* static const char*
polymorphic_string_as_utf8(id string) { polymorphic_string_as_utf8(id string) {
@@ -306,6 +306,28 @@ static NSUInteger getStyleMask(_GLFWwindow* window)
} }
CGDirectDisplayID displayIDForWindow(_GLFWwindow *w) {
NSWindow *nw = w->ns.object;
NSDictionary *dict = [nw.screen deviceDescription];
NSNumber *displayIDns = dict[@"NSScreenNumber"];
if (displayIDns) return [displayIDns unsignedIntValue];
return (CGDirectDisplayID)-1;
}
static unsigned long long display_link_shutdown_timer = 0;
#define DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL s_to_monotonic_t(30ll)
void
_glfwShutdownCVDisplayLink(unsigned long long timer_id UNUSED, void *user_data UNUSED) {
display_link_shutdown_timer = 0;
for (size_t i = 0; i < _glfw.ns.displayLinks.count; i++) {
_GLFWDisplayLinkNS *dl = &_glfw.ns.displayLinks.entries[i];
if (dl->displayLink) CVDisplayLinkStop(dl->displayLink);
dl->lastRenderFrameRequestedAt = 0;
dl->first_unserviced_render_frame_request_at = 0;
}
}
static void static void
requestRenderFrame(_GLFWwindow *w, GLFWcocoarenderframefun callback) { requestRenderFrame(_GLFWwindow *w, GLFWcocoarenderframefun callback) {
if (!callback) { if (!callback) {
@@ -315,7 +337,46 @@ requestRenderFrame(_GLFWwindow *w, GLFWcocoarenderframefun callback) {
} }
w->ns.renderFrameCallback = callback; w->ns.renderFrameCallback = callback;
w->ns.renderFrameRequested = true; w->ns.renderFrameRequested = true;
_glfwRequestRenderFrame(w); CGDirectDisplayID displayID = displayIDForWindow(w);
if (display_link_shutdown_timer) {
_glfwPlatformUpdateTimer(display_link_shutdown_timer, DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL, true);
} else {
display_link_shutdown_timer = _glfwPlatformAddTimer(DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL, false, _glfwShutdownCVDisplayLink, NULL, NULL);
}
monotonic_t now = glfwGetTime();
bool found_display_link = false;
_GLFWDisplayLinkNS *dl = NULL;
for (size_t i = 0; i < _glfw.ns.displayLinks.count; i++) {
dl = &_glfw.ns.displayLinks.entries[i];
if (dl->displayID == displayID) {
found_display_link = true;
dl->lastRenderFrameRequestedAt = now;
if (!dl->first_unserviced_render_frame_request_at) dl->first_unserviced_render_frame_request_at = now;
if (!CVDisplayLinkIsRunning(dl->displayLink)) CVDisplayLinkStart(dl->displayLink);
else if (now - dl->first_unserviced_render_frame_request_at > s_to_monotonic_t(1ll)) {
// display link is stuck need to recreate it because Apple can't even
// get a simple timer right
CVDisplayLinkRelease(dl->displayLink); dl->displayLink = nil;
dl->first_unserviced_render_frame_request_at = now;
_glfw_create_cv_display_link(dl);
_glfwInputError(GLFW_PLATFORM_ERROR,
"CVDisplayLink stuck possibly because of sleep/screensaver + Apple's incompetence, recreating.");
if (!CVDisplayLinkIsRunning(dl->displayLink)) CVDisplayLinkStart(dl->displayLink);
}
} else if (dl->displayLink && dl->lastRenderFrameRequestedAt && now - dl->lastRenderFrameRequestedAt >= DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL) {
CVDisplayLinkStop(dl->displayLink);
dl->lastRenderFrameRequestedAt = 0;
dl->first_unserviced_render_frame_request_at = 0;
}
}
if (!found_display_link) {
dl = _glfw_create_display_link(displayID);
if (dl) {
dl->lastRenderFrameRequestedAt = now;
dl->first_unserviced_render_frame_request_at = now;
if (!CVDisplayLinkIsRunning(dl->displayLink)) CVDisplayLinkStart(dl->displayLink);
}
}
} }
void void
@@ -940,7 +1001,6 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
(void)event; (void)event;
if (!window) return; if (!window) return;
_glfwInputCursorEnter(window, false); _glfwInputCursorEnter(window, false);
[[NSCursor arrowCursor] set];
} }
- (void)mouseEntered:(NSEvent *)event - (void)mouseEntered:(NSEvent *)event
@@ -948,16 +1008,17 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
(void)event; (void)event;
if (!window) return; if (!window) return;
_glfwInputCursorEnter(window, true); _glfwInputCursorEnter(window, true);
updateCursorImage(window);
} }
- (void)viewDidChangeEffectiveAppearance - (void)viewDidChangeEffectiveAppearance
{ {
static GLFWColorScheme appearance = GLFW_COLOR_SCHEME_NO_PREFERENCE; static int appearance = 0;
GLFWColorScheme new_appearance = glfwGetCurrentSystemColorTheme(true); if (_glfw.callbacks.system_color_theme_change) {
if (new_appearance != appearance) { int new_appearance = glfwGetCurrentSystemColorTheme();
appearance = new_appearance; if (new_appearance != appearance) {
_glfwInputColorScheme(appearance, false); appearance = new_appearance;
_glfw.callbacks.system_color_theme_change(appearance);
}
} }
} }
@@ -1482,20 +1543,16 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
} }
} }
// insertText can be called multiple times for a single key event // insertText can be called multiple times for a single key event
size_t existing_length = strnlen(_glfw.ns.text, sizeof(_glfw.ns.text)); char *s = _glfw.ns.text + strnlen(_glfw.ns.text, sizeof(_glfw.ns.text));
size_t required_length = strlen(utf8) + 1; snprintf(s, sizeof(_glfw.ns.text) - (s - _glfw.ns.text), "%s", utf8);
size_t available_length = sizeof(_glfw.ns.text) - existing_length; _glfw.ns.text[sizeof(_glfw.ns.text) - 1] = 0;
if (available_length >= required_length) { if ((!in_key_handler || in_key_handler == 2) && _glfw.ns.text[0]) {
memcpy(_glfw.ns.text + existing_length, utf8, required_length); // copies the null terminator from utf8 as well if (!is_ascii_control_char(_glfw.ns.text[0])) {
_glfw.ns.text[sizeof(_glfw.ns.text) - 1] = 0; debug_key("Sending text to kitty from insertText called from %s: %s\n", in_key_handler ? "flagsChanged" : "event loop", _glfw.ns.text);
if ((!in_key_handler || in_key_handler == 2) && _glfw.ns.text[0]) { GLFWkeyevent glfw_keyevent = {.text=_glfw.ns.text, .ime_state=GLFW_IME_COMMIT_TEXT};
if (!is_ascii_control_char(_glfw.ns.text[0])) { _glfwInputKeyboard(window, &glfw_keyevent);
debug_key("Sending text to kitty from insertText called from %s: %s\n", in_key_handler ? "flagsChanged" : "event loop", _glfw.ns.text);
GLFWkeyevent glfw_keyevent = {.text=_glfw.ns.text, .ime_state=GLFW_IME_COMMIT_TEXT};
_glfwInputKeyboard(window, &glfw_keyevent);
}
_glfw.ns.text[0] = 0;
} }
_glfw.ns.text[0] = 0;
} }
} }
@@ -2278,6 +2335,24 @@ bool _glfwPlatformRawMouseMotionSupported(void)
return false; return false;
} }
void
_glfwDispatchRenderFrame(CGDirectDisplayID displayID) {
_GLFWwindow *w = _glfw.windowListHead;
while (w) {
if (w->ns.renderFrameRequested && displayID == displayIDForWindow(w)) {
w->ns.renderFrameRequested = false;
w->ns.renderFrameCallback((GLFWwindow*)w);
}
w = w->next;
}
for (size_t i = 0; i < _glfw.ns.displayLinks.count; i++) {
_GLFWDisplayLinkNS *dl = &_glfw.ns.displayLinks.entries[i];
if (dl->displayID == displayID) {
dl->first_unserviced_render_frame_request_at = 0;
}
}
}
void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos) void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos)
{ {
const NSRect contentRect = [window->ns.view frame]; const NSRect contentRect = [window->ns.view frame];
@@ -2570,20 +2645,6 @@ bool _glfwPlatformIsFullscreen(_GLFWwindow* w, unsigned int flags) {
return sm & NSWindowStyleMaskFullScreen; return sm & NSWindowStyleMaskFullScreen;
} }
static void
make_window_fullscreen_after_show(unsigned long long timer_id, void* data) {
(void)timer_id;
unsigned long long window_id = (uintptr_t)data;
for (_GLFWwindow *w = _glfw.windowListHead; w; w = w->next) {
if (w->id == window_id) {
NSWindow *window = w->ns.object;
[window toggleFullScreen: nil];
update_titlebar_button_visibility_after_fullscreen_transition(w, false, true);
break;
}
}
}
bool _glfwPlatformToggleFullscreen(_GLFWwindow* w, unsigned int flags) { bool _glfwPlatformToggleFullscreen(_GLFWwindow* w, unsigned int flags) {
NSWindow *window = w->ns.object; NSWindow *window = w->ns.object;
bool made_fullscreen = true; bool made_fullscreen = true;
@@ -2625,13 +2686,6 @@ bool _glfwPlatformToggleFullscreen(_GLFWwindow* w, unsigned int flags) {
[w->ns.delegate performSelector:@selector(windowDidResize:) withObject:notification afterDelay:0]; [w->ns.delegate performSelector:@selector(windowDidResize:) withObject:notification afterDelay:0];
} else { } else {
bool in_fullscreen = sm & NSWindowStyleMaskFullScreen; bool in_fullscreen = sm & NSWindowStyleMaskFullScreen;
if (!in_fullscreen && !_glfwPlatformWindowVisible(w)) {
// Bug in Apple's fullscreen implementation causes fullscreen to
// not work before window is shown (at creation) if another window
// is already fullscreen. Le sigh. https://github.com/kovidgoyal/kitty/issues/7448
_glfwPlatformAddTimer(0, false, make_window_fullscreen_after_show, (void*)(uintptr_t)(w->id), NULL);
return made_fullscreen;
}
if (in_fullscreen) made_fullscreen = false; if (in_fullscreen) made_fullscreen = false;
[window toggleFullScreen: nil]; [window toggleFullScreen: nil];
} }
@@ -3084,17 +3138,12 @@ GLFWAPI void glfwCocoaSetWindowChrome(GLFWwindow *w, unsigned int color, bool us
// event. See https://github.com/kovidgoyal/kitty/issues/7106 // event. See https://github.com/kovidgoyal/kitty/issues/7106
NSWindowStyleMask fsmask = current_style_mask & NSWindowStyleMaskFullScreen; NSWindowStyleMask fsmask = current_style_mask & NSWindowStyleMaskFullScreen;
window->ns.pre_full_screen_style_mask = getStyleMask(window); window->ns.pre_full_screen_style_mask = getStyleMask(window);
if (in_fullscreen && window->ns.in_traditional_fullscreen) { [window->ns.object setStyleMask:window->ns.pre_full_screen_style_mask | fsmask];
[window->ns.object setStyleMask:NSWindowStyleMaskBorderless];
} else {
[window->ns.object setStyleMask:window->ns.pre_full_screen_style_mask | fsmask];
}
// HACK: Changing the style mask can cause the first responder to be cleared // HACK: Changing the style mask can cause the first responder to be cleared
[window->ns.object makeFirstResponder:window->ns.view]; [window->ns.object makeFirstResponder:window->ns.view];
}} }}
GLFWAPI GLFWColorScheme glfwGetCurrentSystemColorTheme(bool query_if_unintialized) { GLFWAPI int glfwGetCurrentSystemColorTheme(void) {
(void)query_if_unintialized;
int theme_type = 0; int theme_type = 0;
NSAppearance *changedAppearance = NSApp.effectiveAppearance; NSAppearance *changedAppearance = NSApp.effectiveAppearance;
NSAppearanceName newAppearance = [changedAppearance bestMatchFromAppearancesWithNames:@[NSAppearanceNameAqua, NSAppearanceNameDarkAqua]]; NSAppearanceName newAppearance = [changedAppearance bestMatchFromAppearancesWithNames:@[NSAppearanceNameAqua, NSAppearanceNameDarkAqua]];

4
glfw/context.c vendored
View File

@@ -434,11 +434,11 @@ bool _glfwStringInExtensionString(const char* string, const char* extensions)
GLFWAPI void glfwMakeContextCurrent(GLFWwindow* handle) GLFWAPI void glfwMakeContextCurrent(GLFWwindow* handle)
{ {
_GLFW_REQUIRE_INIT();
_GLFWwindow* window = (_GLFWwindow*) handle; _GLFWwindow* window = (_GLFWwindow*) handle;
_GLFWwindow* previous = _glfwPlatformGetTls(&_glfw.contextSlot); _GLFWwindow* previous = _glfwPlatformGetTls(&_glfw.contextSlot);
_GLFW_REQUIRE_INIT();
if (window && window->context.client == GLFW_NO_API) if (window && window->context.client == GLFW_NO_API)
{ {
_glfwInputError(GLFW_NO_WINDOW_CONTEXT, _glfwInputError(GLFW_NO_WINDOW_CONTEXT,

37
glfw/dbus_glfw.c vendored
View File

@@ -224,31 +224,23 @@ format_message_error(DBusError *err) {
static void static void
method_reply_received(DBusPendingCall *pending, void *user_data) { method_reply_received(DBusPendingCall *pending, void *user_data) {
MethodResponse *res = (MethodResponse*)user_data; MethodResponse *res = (MethodResponse*)user_data;
RAII_MSG(msg, dbus_pending_call_steal_reply(pending)); DBusMessage *msg = dbus_pending_call_steal_reply(pending);
if (msg) { if (msg) {
DBusError err; DBusError err;
dbus_error_init(&err); dbus_error_init(&err);
if (dbus_set_error_from_message(&err, msg)) res->callback(NULL, format_message_error(&err), res->user_data); if (dbus_set_error_from_message(&err, msg)) res->callback(NULL, format_message_error(&err), res->user_data);
else res->callback(msg, NULL, res->user_data); else res->callback(msg, NULL, res->user_data);
dbus_message_unref(msg);
} }
} }
bool bool
call_method_with_msg(DBusConnection *conn, DBusMessage *msg, int timeout, dbus_pending_callback callback, void *user_data, bool block) { call_method_with_msg(DBusConnection *conn, DBusMessage *msg, int timeout, dbus_pending_callback callback, void *user_data) {
bool retval = false; bool retval = false;
#define REPORT(errs) _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to call DBUS method: node=%s path=%s interface=%s method=%s, with error: %s", dbus_message_get_destination(msg), dbus_message_get_path(msg), dbus_message_get_interface(msg), dbus_message_get_member(msg), errs) #define REPORT(errs) _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to call DBUS method: node=%s path=%s interface=%s method=%s, with error: %s", dbus_message_get_destination(msg), dbus_message_get_path(msg), dbus_message_get_interface(msg), dbus_message_get_member(msg), errs)
if (callback) { if (callback) {
DBusPendingCall *pending = NULL; DBusPendingCall *pending = NULL;
if (block) { if (dbus_connection_send_with_reply(conn, msg, &pending, timeout)) {
DBusError error; dbus_error_init(&error);
RAII_MSG(reply, dbus_connection_send_with_reply_and_block(session_bus, msg, timeout, &error));
if (dbus_error_is_set(&error)) {
callback(reply, error.message, user_data);
return false;
} else if (reply) {
callback(reply, NULL, user_data);
} else return false;
} else if (dbus_connection_send_with_reply(conn, msg, &pending, timeout)) {
MethodResponse *res = malloc(sizeof(MethodResponse)); MethodResponse *res = malloc(sizeof(MethodResponse));
if (!res) return false; if (!res) return false;
res->callback = callback; res->callback = callback;
@@ -270,18 +262,19 @@ call_method_with_msg(DBusConnection *conn, DBusMessage *msg, int timeout, dbus_p
} }
static bool static bool
call_method(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, int timeout, dbus_pending_callback callback, void *user_data, bool blocking, va_list ap) { call_method(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, int timeout, dbus_pending_callback callback, void *user_data, va_list ap) {
if (!conn || !path) return false; if (!conn || !path) return false;
RAII_MSG(msg, dbus_message_new_method_call(node, path, interface, method)); DBusMessage *msg = dbus_message_new_method_call(node, path, interface, method);
if (!msg) return false; if (!msg) return false;
bool retval = false; bool retval = false;
int firstarg = va_arg(ap, int); int firstarg = va_arg(ap, int);
if ((firstarg == DBUS_TYPE_INVALID) || dbus_message_append_args_valist(msg, firstarg, ap)) { if ((firstarg == DBUS_TYPE_INVALID) || dbus_message_append_args_valist(msg, firstarg, ap)) {
retval = call_method_with_msg(conn, msg, timeout, callback, user_data, blocking); retval = call_method_with_msg(conn, msg, timeout, callback, user_data);
} else { } else {
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to call DBUS method: %s on node: %s and interface: %s could not add arguments", method, node, interface); _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to call DBUS method: %s on node: %s and interface: %s could not add arguments", method, node, interface);
} }
dbus_message_unref(msg);
return retval; return retval;
} }
@@ -291,17 +284,7 @@ glfw_dbus_call_method_with_reply(DBusConnection *conn, const char *node, const c
bool retval; bool retval;
va_list ap; va_list ap;
va_start(ap, user_data); va_start(ap, user_data);
retval = call_method(conn, node, path, interface, method, timeout, callback, user_data, false, ap); retval = call_method(conn, node, path, interface, method, timeout, callback, user_data, ap);
va_end(ap);
return retval;
}
bool
glfw_dbus_call_blocking_method(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, int timeout, dbus_pending_callback callback, void* user_data, ...) {
bool retval;
va_list ap;
va_start(ap, user_data);
retval = call_method(conn, node, path, interface, method, timeout, callback, user_data, true, ap);
va_end(ap); va_end(ap);
return retval; return retval;
} }
@@ -311,7 +294,7 @@ glfw_dbus_call_method_no_reply(DBusConnection *conn, const char *node, const cha
bool retval; bool retval;
va_list ap; va_list ap;
va_start(ap, method); va_start(ap, method);
retval = call_method(conn, node, path, interface, method, DBUS_TIMEOUT_USE_DEFAULT, NULL, NULL, false, ap); retval = call_method(conn, node, path, interface, method, DBUS_TIMEOUT_USE_DEFAULT, NULL, NULL, ap);
va_end(ap); va_end(ap);
return retval; return retval;
} }

7
glfw/dbus_glfw.h vendored
View File

@@ -30,9 +30,6 @@
#include <dbus/dbus.h> #include <dbus/dbus.h>
#include "backend_utils.h" #include "backend_utils.h"
static inline void cleanup_msg(void *p) { DBusMessage *m = *(DBusMessage**)p; if (m) dbus_message_unref(m); m = NULL; }
#define RAII_MSG(name, initializer) __attribute__((cleanup(cleanup_msg))) DBusMessage *name = initializer
typedef void(*dbus_pending_callback)(DBusMessage *msg, const char* err, void* data); typedef void(*dbus_pending_callback)(DBusMessage *msg, const char* err, void* data);
typedef struct { typedef struct {
@@ -45,13 +42,11 @@ void glfw_dbus_terminate(_GLFWDBUSData *dbus);
DBusConnection* glfw_dbus_connect_to(const char *path, const char* err_msg, const char* name, bool register_on_bus); DBusConnection* glfw_dbus_connect_to(const char *path, const char* err_msg, const char* name, bool register_on_bus);
void glfw_dbus_close_connection(DBusConnection *conn); void glfw_dbus_close_connection(DBusConnection *conn);
bool bool
call_method_with_msg(DBusConnection *conn, DBusMessage *msg, int timeout, dbus_pending_callback callback, void *user_data, bool block); call_method_with_msg(DBusConnection *conn, DBusMessage *msg, int timeout, dbus_pending_callback callback, void *user_data);
bool bool
glfw_dbus_call_method_no_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...); glfw_dbus_call_method_no_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...);
bool bool
glfw_dbus_call_method_with_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, int timeout_ms, dbus_pending_callback callback, void *user_data, ...); glfw_dbus_call_method_with_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, int timeout_ms, dbus_pending_callback callback, void *user_data, ...);
bool
glfw_dbus_call_blocking_method(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, int timeout, dbus_pending_callback callback, void* user_data, ...);
void glfw_dbus_dispatch(DBusConnection *); void glfw_dbus_dispatch(DBusConnection *);
void glfw_dbus_session_bus_dispatch(void); void glfw_dbus_session_bus_dispatch(void);
bool glfw_dbus_get_args(DBusMessage *msg, const char *failmsg, ...); bool glfw_dbus_get_args(DBusMessage *msg, const char *failmsg, ...);

View File

@@ -64,10 +64,6 @@ class Env:
vcs_rev: str = '' vcs_rev: str = ''
binary_arch: BinaryArch = BinaryArch() binary_arch: BinaryArch = BinaryArch()
native_optimizations: bool = False native_optimizations: bool = False
primary_version: int = 0
secondary_version: int = 0
xt_version: str = ''
has_copy_file_range: bool = False
# glfw stuff # glfw stuff
all_headers: List[str] = [] all_headers: List[str] = []
@@ -122,10 +118,6 @@ class Env:
ans.vcs_rev = self.vcs_rev ans.vcs_rev = self.vcs_rev
ans.binary_arch = self.binary_arch ans.binary_arch = self.binary_arch
ans.native_optimizations = self.native_optimizations ans.native_optimizations = self.native_optimizations
ans.primary_version = self.primary_version
ans.secondary_version = self.secondary_version
ans.xt_version = self.xt_version
ans.has_copy_file_range = self.has_copy_file_range
return ans return ans
@@ -319,15 +311,13 @@ def generate_wrappers(glfw_header: str) -> None:
int glfwGetNativeKeyForName(const char* key_name, int case_sensitive) int glfwGetNativeKeyForName(const char* key_name, int case_sensitive)
void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long id, GLFWwaylandframecallbackfunc callback) void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long id, GLFWwaylandframecallbackfunc callback)
void glfwWaylandActivateWindow(GLFWwindow *handle, const char *activation_token) void glfwWaylandActivateWindow(GLFWwindow *handle, const char *activation_token)
const char* glfwWaylandMissingCapabilities(void)
void glfwWaylandRunWithActivationToken(GLFWwindow *handle, GLFWactivationcallback cb, void *cb_data) void glfwWaylandRunWithActivationToken(GLFWwindow *handle, GLFWactivationcallback cb, void *cb_data)
bool glfwWaylandSetTitlebarColor(GLFWwindow *handle, uint32_t color, bool use_system_color) bool glfwWaylandSetTitlebarColor(GLFWwindow *handle, uint32_t color, bool use_system_color)
void glfwWaylandRedrawCSDWindowTitle(GLFWwindow *handle) void glfwWaylandRedrawCSDWindowTitle(GLFWwindow *handle)
bool glfwWaylandIsWindowFullyCreated(GLFWwindow *handle) void glfwWaylandSetupLayerShellForNextWindow(GLFWLayerShellConfig c)
bool glfwWaylandBeep(GLFWwindow *handle)
void glfwWaylandSetupLayerShellForNextWindow(const GLFWLayerShellConfig *c)
pid_t glfwWaylandCompositorPID(void) pid_t glfwWaylandCompositorPID(void)
unsigned long long glfwDBusUserNotify(const GLFWDBUSNotificationData *n, GLFWDBusnotificationcreatedfun callback, void *data) unsigned long long glfwDBusUserNotify(const char *app_name, const char* icon, const char *summary, const char *body, \
const char *action_text, int32_t timeout, GLFWDBusnotificationcreatedfun callback, void *data)
void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler) void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler)
int glfwSetX11LaunchCommand(GLFWwindow *handle, char **argv, int argc) int glfwSetX11LaunchCommand(GLFWwindow *handle, char **argv, int argc)
void glfwSetX11WindowAsDock(int32_t x11_window_id) void glfwSetX11WindowAsDock(int32_t x11_window_id)
@@ -365,7 +355,7 @@ typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
typedef void (* GLFWcocoarenderframefun)(GLFWwindow*); typedef void (* GLFWcocoarenderframefun)(GLFWwindow*);
typedef void (*GLFWwaylandframecallbackfunc)(unsigned long long id); typedef void (*GLFWwaylandframecallbackfunc)(unsigned long long id);
typedef void (*GLFWDBusnotificationcreatedfun)(unsigned long long, uint32_t, void*); typedef void (*GLFWDBusnotificationcreatedfun)(unsigned long long, uint32_t, void*);
typedef void (*GLFWDBusnotificationactivatedfun)(uint32_t, int, const char*); typedef void (*GLFWDBusnotificationactivatedfun)(uint32_t, const char*);
{} {}
const char* load_glfw(const char* path); const char* load_glfw(const char* path);

46
glfw/glfw3.h vendored
View File

@@ -540,12 +540,6 @@ typedef enum GLFWMouseButton {
} GLFWMouseButton; } GLFWMouseButton;
/*! @} */ /*! @} */
typedef enum GLFWColorScheme {
GLFW_COLOR_SCHEME_NO_PREFERENCE = 0,
GLFW_COLOR_SCHEME_DARK = 1,
GLFW_COLOR_SCHEME_LIGHT = 2
} GLFWColorScheme;
/*! @defgroup joysticks Joysticks /*! @defgroup joysticks Joysticks
* @brief Joystick IDs. * @brief Joystick IDs.
* *
@@ -1168,7 +1162,6 @@ typedef enum {
* macOS specific [init hint](@ref GLFW_COCOA_MENUBAR_hint). * macOS specific [init hint](@ref GLFW_COCOA_MENUBAR_hint).
*/ */
#define GLFW_COCOA_MENUBAR 0x00051002 #define GLFW_COCOA_MENUBAR 0x00051002
#define GLFW_WAYLAND_IME 0x00051003
/*! @} */ /*! @} */
#define GLFW_DONT_CARE -1 #define GLFW_DONT_CARE -1
@@ -1300,34 +1293,21 @@ typedef struct GLFWkeyevent
bool fake_event_on_focus_change; bool fake_event_on_focus_change;
} GLFWkeyevent; } GLFWkeyevent;
typedef enum { GLFW_LAYER_SHELL_NONE, GLFW_LAYER_SHELL_BACKGROUND, GLFW_LAYER_SHELL_PANEL, GLFW_LAYER_SHELL_TOP, GLFW_LAYER_SHELL_OVERLAY } GLFWLayerShellType; typedef enum { GLFW_LAYER_SHELL_NONE, GLFW_LAYER_SHELL_BACKGROUND, GLFW_LAYER_SHELL_PANEL } GLFWLayerShellType;
typedef enum { GLFW_EDGE_TOP, GLFW_EDGE_BOTTOM, GLFW_EDGE_LEFT, GLFW_EDGE_RIGHT, GLFW_EDGE_CENTER, GLFW_EDGE_NONE } GLFWEdge; typedef enum { GLFW_EDGE_TOP, GLFW_EDGE_BOTTOM, GLFW_EDGE_LEFT, GLFW_EDGE_RIGHT } GLFWEdge;
typedef enum { GLFW_FOCUS_NOT_ALLOWED, GLFW_FOCUS_EXCLUSIVE, GLFW_FOCUS_ON_DEMAND} GLFWFocusPolicy; typedef enum { GLFW_FOCUS_NOT_ALLOWED, GLFW_FOCUS_EXCLUSIVE, GLFW_FOCUS_ON_DEMAND} GLFWFocusPolicy;
typedef struct GLFWLayerShellConfig { typedef struct GLFWLayerShellConfig {
GLFWLayerShellType type; GLFWLayerShellType type;
GLFWEdge edge; GLFWEdge edge;
char output_name[64]; const char *output_name;
GLFWFocusPolicy focus_policy; GLFWFocusPolicy focus_policy;
unsigned x_size_in_cells; unsigned size_in_cells;
unsigned y_size_in_cells;
unsigned requested_top_margin;
unsigned requested_left_margin;
unsigned requested_bottom_margin;
unsigned requested_right_margin;
int requested_exclusive_zone;
unsigned override_exclusive_zone;
void (*size_callback)(GLFWwindow *window, const struct GLFWLayerShellConfig *config, unsigned monitor_width, unsigned monitor_height, uint32_t *width, uint32_t *height); void (*size_callback)(GLFWwindow *window, const struct GLFWLayerShellConfig *config, unsigned monitor_width, unsigned monitor_height, uint32_t *width, uint32_t *height);
struct { double xdpi, ydpi, xscale, yscale; } expected;
} GLFWLayerShellConfig; } GLFWLayerShellConfig;
typedef struct GLFWDBUSNotificationData {
const char *app_name, *icon, *summary, *body, *category, **actions; size_t num_actions;
int32_t timeout; uint8_t urgency; uint32_t replaces; int muted;
} GLFWDBUSNotificationData;
/*! @brief The function pointer type for error callbacks. /*! @brief The function pointer type for error callbacks.
* *
* This is the function pointer type for error callbacks. An error callback * This is the function pointer type for error callbacks. An error callback
@@ -1433,22 +1413,20 @@ typedef void (* GLFWwindowclosefun)(GLFWwindow*);
*/ */
typedef void (* GLFWapplicationclosefun)(int); typedef void (* GLFWapplicationclosefun)(int);
/*! @brief The function pointer type for system color theme change callbacks. /*! @brief The function pointer type for system color theme change callbacks.
* *
* This is the function pointer type for system color theme changes. * This is the function pointer type for system color theme changes.
* @code * @code
* void function_name(GLFWColorScheme theme_type, bool is_initial_value) * void function_name(int theme_type)
* @endcode * @endcode
* *
* @param[in] theme_type 0 for unknown, 1 for dark and 2 for light * @param[in] theme_type 0 for unknown, 1 for dark and 2 for light
* @param[in] is_initial_value true if this is the initial read of the color theme on systems where it is asynchronous such as Linux
* *
* @sa @ref glfwSetSystemColorThemeChangeCallback * @sa @ref glfwSetSystemColorThemeChangeCallback
* *
* @ingroup window * @ingroup window
*/ */
typedef void (* GLFWsystemcolorthemechangefun)(GLFWColorScheme, bool); typedef void (* GLFWsystemcolorthemechangefun)(int);
/*! @brief The function pointer type for window content refresh callbacks. /*! @brief The function pointer type for window content refresh callbacks.
@@ -1800,7 +1778,7 @@ typedef void (* GLFWjoystickfun)(int,int);
typedef void (* GLFWuserdatafun)(unsigned long long, void*); typedef void (* GLFWuserdatafun)(unsigned long long, void*);
typedef void (* GLFWtickcallback)(void*); typedef void (* GLFWtickcallback)(void*);
typedef void (* GLFWactivationcallback)(GLFWwindow *window, const char *token, void *data); typedef void (* GLFWactivationcallback)(GLFWwindow *window, const char *token, void *data);
typedef bool (* GLFWdrawtextfun)(GLFWwindow *window, const char *text, uint32_t fg, uint32_t bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset, size_t right_margin, bool is_single_glyph); typedef bool (* GLFWdrawtextfun)(GLFWwindow *window, const char *text, uint32_t fg, uint32_t bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset, size_t right_margin);
typedef char* (* GLFWcurrentselectionfun)(void); typedef char* (* GLFWcurrentselectionfun)(void);
typedef bool (* GLFWhascurrentselectionfun)(void); typedef bool (* GLFWhascurrentselectionfun)(void);
typedef void (* GLFWclipboarddatafreefun)(void* data); typedef void (* GLFWclipboarddatafreefun)(void* data);
@@ -1816,7 +1794,6 @@ typedef enum {
typedef GLFWDataChunk (* GLFWclipboarditerfun)(const char *mime_type, void *iter, GLFWClipboardType ctype); typedef GLFWDataChunk (* GLFWclipboarditerfun)(const char *mime_type, void *iter, GLFWClipboardType ctype);
typedef bool (* GLFWclipboardwritedatafun)(void *object, const char *data, size_t sz); typedef bool (* GLFWclipboardwritedatafun)(void *object, const char *data, size_t sz);
typedef bool (* GLFWimecursorpositionfun)(GLFWwindow *window, GLFWIMEUpdateEvent *ev); typedef bool (* GLFWimecursorpositionfun)(GLFWwindow *window, GLFWIMEUpdateEvent *ev);
typedef void (* GLFWclipboardlostfun )(GLFWClipboardType);
/*! @brief Video mode type. /*! @brief Video mode type.
* *
@@ -1967,7 +1944,7 @@ typedef struct GLFWgamepadstate
* *
* @ingroup init * @ingroup init
*/ */
GLFWAPI int glfwInit(monotonic_t start_time, bool *supports_window_occlusion); GLFWAPI int glfwInit(monotonic_t start_time);
GLFWAPI void glfwRunMainLoop(GLFWtickcallback callback, void *callback_data); GLFWAPI void glfwRunMainLoop(GLFWtickcallback callback, void *callback_data);
GLFWAPI void glfwStopMainLoop(void); GLFWAPI void glfwStopMainLoop(void);
GLFWAPI unsigned long long glfwAddTimer(monotonic_t interval, bool repeats, GLFWuserdatafun callback, void * callback_data, GLFWuserdatafun free_callback); GLFWAPI unsigned long long glfwAddTimer(monotonic_t interval, bool repeats, GLFWuserdatafun callback, void * callback_data, GLFWuserdatafun free_callback);
@@ -3003,6 +2980,10 @@ GLFWAPI void glfwSetWindowTitle(GLFWwindow* window, const char* title);
* [Bundle Programming Guide](https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFBundles/) * [Bundle Programming Guide](https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFBundles/)
* in the Mac Developer Library. * in the Mac Developer Library.
* *
* @remark @wayland There is no existing protocol to change an icon, the
* window will thus inherit the one defined in the application's desktop file.
* This function will emit @ref GLFW_FEATURE_UNAVAILABLE.
*
* @thread_safety This function must only be called from the main thread. * @thread_safety This function must only be called from the main thread.
* *
* @sa @ref window_icon * @sa @ref window_icon
@@ -3988,8 +3969,7 @@ GLFWAPI GLFWwindowsizefun glfwSetWindowSizeCallback(GLFWwindow* window, GLFWwind
GLFWAPI GLFWwindowclosefun glfwSetWindowCloseCallback(GLFWwindow* window, GLFWwindowclosefun callback); GLFWAPI GLFWwindowclosefun glfwSetWindowCloseCallback(GLFWwindow* window, GLFWwindowclosefun callback);
GLFWAPI GLFWapplicationclosefun glfwSetApplicationCloseCallback(GLFWapplicationclosefun callback); GLFWAPI GLFWapplicationclosefun glfwSetApplicationCloseCallback(GLFWapplicationclosefun callback);
GLFWAPI GLFWsystemcolorthemechangefun glfwSetSystemColorThemeChangeCallback(GLFWsystemcolorthemechangefun callback); GLFWAPI GLFWsystemcolorthemechangefun glfwSetSystemColorThemeChangeCallback(GLFWsystemcolorthemechangefun callback);
GLFWAPI GLFWclipboardlostfun glfwSetClipboardLostCallback(GLFWclipboardlostfun callback); GLFWAPI int glfwGetCurrentSystemColorTheme(void);
GLFWAPI GLFWColorScheme glfwGetCurrentSystemColorTheme(bool query_if_unintialized);
/*! @brief Sets the refresh callback for the specified window. /*! @brief Sets the refresh callback for the specified window.
* *

9
glfw/ibus_glfw.c vendored
View File

@@ -46,7 +46,7 @@
#include "internal.h" #include "internal.h"
#include "ibus_glfw.h" #include "ibus_glfw.h"
#define debug debug_input #define debug(...) if (_glfw.hints.init.debugKeyboard) printf(__VA_ARGS__);
static const char IBUS_SERVICE[] = "org.freedesktop.IBus"; static const char IBUS_SERVICE[] = "org.freedesktop.IBus";
static const char IBUS_PATH[] = "/org/freedesktop/IBus"; static const char IBUS_PATH[] = "/org/freedesktop/IBus";
static const char IBUS_INTERFACE[] = "org.freedesktop.IBus"; static const char IBUS_INTERFACE[] = "org.freedesktop.IBus";
@@ -325,12 +325,7 @@ get_ibus_address_file_name(void) {
} }
offset = snprintf(ans, sizeof(ans), "%s/.config", conf_env); offset = snprintf(ans, sizeof(ans), "%s/.config", conf_env);
} }
DBusError err; char *key = dbus_get_local_machine_id();
char *key = dbus_try_get_local_machine_id(&err);
if (!key) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Cannot connect to IBUS as could not get DBUS local machine id with error %s: %s", err.name ? err.name : "", err.message ? err.message : "");
return NULL;
}
snprintf(ans + offset, sizeof(ans) - offset, "/ibus/bus/%s-%s-%s", key, host, disp_num); snprintf(ans + offset, sizeof(ans) - offset, "/ibus/bus/%s-%s-%s", key, host, disp_num);
dbus_free(key); dbus_free(key);
return ans; return ans;

23
glfw/init.c vendored
View File

@@ -34,6 +34,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <stdarg.h> #include <stdarg.h>
#include <assert.h>
// The global variables below comprise all mutable global data in GLFW // The global variables below comprise all mutable global data in GLFW
@@ -57,10 +58,7 @@ static _GLFWinitconfig _glfwInitHints = {
.ns = { .ns = {
.menubar = true, // macOS menu bar .menubar = true, // macOS menu bar
.chdir = true // macOS bundle chdir .chdir = true // macOS bundle chdir
}, }
.wl = {
.ime = true, // Wayland IME support
},
}; };
// Terminate the library // Terminate the library
@@ -223,9 +221,8 @@ _glfwDebug(const char *format, ...) {
////// GLFW public API ////// ////// GLFW public API //////
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
GLFWAPI int glfwInit(monotonic_t start_time, bool *supports_window_occlusion) GLFWAPI int glfwInit(monotonic_t start_time)
{ {
*supports_window_occlusion = false;
if (_glfw.initialized) if (_glfw.initialized)
return true; return true;
monotonic_start_time = start_time; monotonic_start_time = start_time;
@@ -234,7 +231,7 @@ GLFWAPI int glfwInit(monotonic_t start_time, bool *supports_window_occlusion)
_glfw.hints.init = _glfwInitHints; _glfw.hints.init = _glfwInitHints;
_glfw.ignoreOSKeyboardProcessing = false; _glfw.ignoreOSKeyboardProcessing = false;
if (!_glfwPlatformInit(supports_window_occlusion)) if (!_glfwPlatformInit())
{ {
terminate(); terminate();
return false; return false;
@@ -300,9 +297,6 @@ GLFWAPI void glfwInitHint(int hint, int value)
case GLFW_COCOA_MENUBAR: case GLFW_COCOA_MENUBAR:
_glfwInitHints.ns.menubar = value; _glfwInitHints.ns.menubar = value;
return; return;
case GLFW_WAYLAND_IME:
_glfwInitHints.wl.ime = value;
return;
} }
_glfwInputError(GLFW_INVALID_ENUM, _glfwInputError(GLFW_INVALID_ENUM,
@@ -388,20 +382,13 @@ GLFWAPI GLFWapplicationclosefun glfwSetApplicationCloseCallback(GLFWapplicationc
return cbfun; return cbfun;
} }
GLFWAPI GLFWsystemcolorthemechangefun glfwSetSystemColorThemeChangeCallback(GLFWsystemcolorthemechangefun cbfun) GLFWAPI GLFWapplicationclosefun glfwSetSystemColorThemeChangeCallback(GLFWsystemcolorthemechangefun cbfun)
{ {
_GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP_POINTERS(_glfw.callbacks.system_color_theme_change, cbfun); _GLFW_SWAP_POINTERS(_glfw.callbacks.system_color_theme_change, cbfun);
return cbfun; return cbfun;
} }
GLFWAPI GLFWclipboardlostfun glfwSetClipboardLostCallback(GLFWclipboardlostfun cbfun)
{
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP_POINTERS(_glfw.callbacks.clipboard_lost, cbfun);
return cbfun;
}
GLFWAPI GLFWdrawtextfun glfwSetDrawTextFunction(GLFWdrawtextfun cbfun) GLFWAPI GLFWdrawtextfun glfwSetDrawTextFunction(GLFWdrawtextfun cbfun)
{ {

13
glfw/input.c vendored
View File

@@ -448,15 +448,6 @@ void _glfwInputJoystickHat(_GLFWjoystick* js, int hat, char value)
js->hats[hat] = value; js->hats[hat] = value;
} }
void _glfwInputColorScheme(GLFWColorScheme value, bool is_initial_value) {
_glfwPlatformInputColorScheme(value);
if (_glfw.callbacks.system_color_theme_change) _glfw.callbacks.system_color_theme_change(value, is_initial_value);
}
void _glfwInputClipboardLost(GLFWClipboardType which) {
if (_glfw.callbacks.clipboard_lost) _glfw.callbacks.clipboard_lost(which);
}
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
////// GLFW internal API ////// ////// GLFW internal API //////
@@ -733,7 +724,9 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* handle, int mode, int value)
window->cursorMode = value; window->cursorMode = value;
_glfwPlatformGetCursorPos(window, &window->virtualCursorPosX, &window->virtualCursorPosY); _glfwPlatformGetCursorPos(window,
&window->virtualCursorPosX,
&window->virtualCursorPosY);
_glfwPlatformSetCursorMode(window, value); _glfwPlatformSetCursorMode(window, value);
} }
else if (mode == GLFW_STICKY_KEYS) else if (mode == GLFW_STICKY_KEYS)

19
glfw/internal.h vendored
View File

@@ -111,13 +111,6 @@ typedef void (* _GLFWdestroycontextfun)(_GLFWwindow*);
#define GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH 0x82fc #define GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH 0x82fc
#define GL_CONTEXT_FLAG_NO_ERROR_BIT_KHR 0x00000008 #define GL_CONTEXT_FLAG_NO_ERROR_BIT_KHR 0x00000008
#define MAX(x, y) __extension__ ({ \
__typeof__ (x) a = (x); __typeof__ (y) b = (y); \
a > b ? a : b;})
#define MIN(x, y) __extension__ ({ \
__typeof__ (x) a = (x); __typeof__ (y) b = (y); \
a < b ? a : b;})
typedef int GLint; typedef int GLint;
typedef unsigned int GLuint; typedef unsigned int GLuint;
typedef unsigned int GLenum; typedef unsigned int GLenum;
@@ -293,9 +286,6 @@ struct _GLFWinitconfig
bool menubar; bool menubar;
bool chdir; bool chdir;
} ns; } ns;
struct {
bool ime;
} wl;
}; };
// Window configuration // Window configuration
@@ -644,7 +634,6 @@ struct _GLFWlibrary
GLFWmonitorfun monitor; GLFWmonitorfun monitor;
GLFWjoystickfun joystick; GLFWjoystickfun joystick;
GLFWapplicationclosefun application_close; GLFWapplicationclosefun application_close;
GLFWclipboardlostfun clipboard_lost;
GLFWsystemcolorthemechangefun system_color_theme_change; GLFWsystemcolorthemechangefun system_color_theme_change;
GLFWdrawtextfun draw_text; GLFWdrawtextfun draw_text;
GLFWcurrentselectionfun get_current_selection; GLFWcurrentselectionfun get_current_selection;
@@ -673,7 +662,7 @@ extern _GLFWlibrary _glfw;
////// GLFW platform API ////// ////// GLFW platform API //////
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
int _glfwPlatformInit(bool*); int _glfwPlatformInit(void);
void _glfwPlatformTerminate(void); void _glfwPlatformTerminate(void);
const char* _glfwPlatformGetVersionString(void); const char* _glfwPlatformGetVersionString(void);
@@ -810,14 +799,11 @@ void _glfwInputWindowCloseRequest(_GLFWwindow* window);
void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor); void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor);
void _glfwInputKeyboard(_GLFWwindow *window, GLFWkeyevent *ev); void _glfwInputKeyboard(_GLFWwindow *window, GLFWkeyevent *ev);
void _glfwInputClipboardLost(GLFWClipboardType which);
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags, int mods); void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags, int mods);
void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods); void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods);
void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos); void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos);
void _glfwInputCursorEnter(_GLFWwindow* window, bool entered); void _glfwInputCursorEnter(_GLFWwindow* window, bool entered);
int _glfwInputDrop(_GLFWwindow* window, const char *mime, const char *text, size_t sz); int _glfwInputDrop(_GLFWwindow* window, const char *mime, const char *text, size_t sz);
void _glfwInputColorScheme(GLFWColorScheme, bool);
void _glfwPlatformInputColorScheme(GLFWColorScheme);
void _glfwInputJoystick(_GLFWjoystick* js, int event); void _glfwInputJoystick(_GLFWjoystick* js, int event);
void _glfwInputJoystickAxis(_GLFWjoystick* js, int axis, float value); void _glfwInputJoystickAxis(_GLFWjoystick* js, int axis, float value);
void _glfwInputJoystickButton(_GLFWjoystick* js, int button, char value); void _glfwInputJoystickButton(_GLFWjoystick* js, int button, char value);
@@ -884,6 +870,3 @@ int _glfwPlatformSetWindowBlur(_GLFWwindow* handle, int value);
char* _glfw_strdup(const char* source); char* _glfw_strdup(const char* source);
void _glfw_free_clipboard_data(_GLFWClipboardData *cd); void _glfw_free_clipboard_data(_GLFWClipboardData *cd);
#define debug_rendering(...) if (_glfw.hints.init.debugRendering) { timed_debug_print(__VA_ARGS__); }
#define debug_input(...) if (_glfw.hints.init.debugKeyboard) { timed_debug_print(__VA_ARGS__); }

View File

@@ -15,14 +15,18 @@
#define DESKTOP_INTERFACE "org.freedesktop.portal.Settings" #define DESKTOP_INTERFACE "org.freedesktop.portal.Settings"
#define GNOME_DESKTOP_NAMESPACE "org.gnome.desktop.interface" #define GNOME_DESKTOP_NAMESPACE "org.gnome.desktop.interface"
#define FDO_DESKTOP_NAMESPACE "org.freedesktop.appearance" #define FDO_DESKTOP_NAMESPACE "org.freedesktop.appearance"
static const char* supported_namespaces[2] = {FDO_DESKTOP_NAMESPACE, GNOME_DESKTOP_NAMESPACE};
#define FDO_APPEARANCE_KEY "color-scheme" #define FDO_APPEARANCE_KEY "color-scheme"
static char theme_name[128] = {0}; static char theme_name[128] = {0};
static int theme_size = -1; static int theme_size = -1;
static GLFWColorScheme appearance = GLFW_COLOR_SCHEME_NO_PREFERENCE; static uint32_t appearance = 0;
static bool cursor_theme_changed = false, appearance_initialized = false; static bool cursor_theme_changed = false;
int
glfw_current_system_color_theme(void) {
return appearance;
}
#define HANDLER(name) static void name(DBusMessage *msg, const char* errmsg, void *data) { \ #define HANDLER(name) static void name(DBusMessage *msg, const char* errmsg, void *data) { \
(void)data; \ (void)data; \
@@ -31,48 +35,12 @@ static bool cursor_theme_changed = false, appearance_initialized = false;
return; \ return; \
} }
HANDLER(get_color_scheme)
uint32_t val;
DBusMessageIter iter, variant_iter;
if (!dbus_message_iter_init(msg, &iter)) return;
dbus_message_iter_recurse(&iter, &variant_iter);
int type = dbus_message_iter_get_arg_type(&variant_iter);
if (type != DBUS_TYPE_UINT32) {
_glfwInputError(GLFW_PLATFORM_ERROR, "ReadOne for color-scheme did not return a uint32"); return;
}
dbus_message_iter_get_basic(&variant_iter, &val);
if (val < 3) appearance = val;
}
GLFWColorScheme
glfw_current_system_color_theme(bool query_if_unintialized) {
if (!appearance_initialized && query_if_unintialized) {
appearance_initialized = true;
DBusConnection *session_bus = glfw_dbus_session_bus();
if (session_bus) {
const char *namespace = FDO_DESKTOP_NAMESPACE, *key = FDO_APPEARANCE_KEY;
glfw_dbus_call_blocking_method(session_bus, DESKTOP_SERVICE, DESKTOP_PATH, DESKTOP_INTERFACE, "ReadOne", DBUS_TIMEOUT_USE_DEFAULT,
get_color_scheme, NULL, DBUS_TYPE_STRING, &namespace, DBUS_TYPE_STRING, &key, DBUS_TYPE_INVALID);
}
}
return appearance;
}
static void static void
process_fdo_setting(const char *key, DBusMessageIter *value) { process_fdo_setting(const char *key, DBusMessageIter *value) {
if (strcmp(key, FDO_APPEARANCE_KEY) == 0) { if (strcmp(key, FDO_APPEARANCE_KEY) == 0) {
if (dbus_message_iter_get_arg_type(value) == DBUS_TYPE_UINT32) { if (dbus_message_iter_get_arg_type(value) == DBUS_TYPE_UINT32) {
uint32_t val; dbus_message_iter_get_basic(value, &appearance);
dbus_message_iter_get_basic(value, &val); if (appearance > 2) appearance = 0;
if (val > 2) val = 0;
if (!appearance_initialized) {
appearance_initialized = true;
if (val != appearance) {
appearance = val;
_glfwInputColorScheme(appearance, true);
}
}
} }
} }
} }
@@ -141,25 +109,22 @@ HANDLER(process_desktop_settings)
if (!dbus_message_iter_next(&array)) break; if (!dbus_message_iter_next(&array)) break;
} }
#undef die #undef die
#ifndef _GLFW_X11
if (cursor_theme_changed) _glfwPlatformChangeCursorTheme(); if (cursor_theme_changed) _glfwPlatformChangeCursorTheme();
#endif
} }
#undef HANDLER #undef HANDLER
static bool static bool
read_desktop_settings(DBusConnection *session_bus) { read_desktop_settings(DBusConnection *session_bus) {
RAII_MSG(msg, dbus_message_new_method_call(DESKTOP_SERVICE, DESKTOP_PATH, DESKTOP_INTERFACE, "ReadAll")); DBusMessage *msg = dbus_message_new_method_call(DESKTOP_SERVICE, DESKTOP_PATH, DESKTOP_INTERFACE, "ReadAll");
if (!msg) return false; if (!msg) return false;
DBusMessageIter iter, array_iter; DBusMessageIter iter, array_iter;
dbus_message_iter_init_append(msg, &iter); dbus_message_iter_init_append(msg, &iter);
if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &array_iter)) { return false; } if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &array_iter)) { dbus_message_unref(msg); return false; }
for (unsigned i = 0; i < arraysz(supported_namespaces); ++i) { if (!dbus_message_iter_close_container(&iter, &array_iter)) { dbus_message_unref(msg); return false; }
if (!dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_STRING, &supported_namespaces[i])) return false; bool ok = call_method_with_msg(session_bus, msg, DBUS_TIMEOUT_USE_DEFAULT, process_desktop_settings, NULL);
} dbus_message_unref(msg);
if (!dbus_message_iter_close_container(&iter, &array_iter)) { return false; } return ok;
return call_method_with_msg(session_bus, msg, DBUS_TIMEOUT_USE_DEFAULT, process_desktop_settings, NULL, false);
} }
void void
@@ -194,8 +159,9 @@ on_color_scheme_change(DBusMessage *message) {
if (val > 2) val = 0; if (val > 2) val = 0;
if (val != appearance) { if (val != appearance) {
appearance = val; appearance = val;
appearance_initialized = true; if (_glfw.callbacks.system_color_theme_change) {
_glfwInputColorScheme(appearance, false); _glfw.callbacks.system_color_theme_change(appearance);
}
} }
} }
break; break;

View File

@@ -12,4 +12,4 @@
void glfw_initialize_desktop_settings(void); void glfw_initialize_desktop_settings(void);
void glfw_current_cursor_theme(const char **theme, int *size); void glfw_current_cursor_theme(const char **theme, int *size);
GLFWColorScheme glfw_current_system_color_theme(bool); int glfw_current_system_color_theme(void);

148
glfw/linux_notify.c vendored
View File

@@ -5,18 +5,15 @@
* Distributed under terms of the GPL3 license. * Distributed under terms of the GPL3 license.
*/ */
#define _POSIX_C_SOURCE 200809L
#include "internal.h" #include "internal.h"
#include "linux_notify.h" #include "linux_notify.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#define NOTIFICATIONS_SERVICE "org.freedesktop.Notifications" #define NOTIFICATIONS_SERVICE "org.freedesktop.Notifications"
#define NOTIFICATIONS_PATH "/org/freedesktop/Notifications" #define NOTIFICATIONS_PATH "/org/freedesktop/Notifications"
#define NOTIFICATIONS_IFACE "org.freedesktop.Notifications" #define NOTIFICATIONS_IFACE "org.freedesktop.Notifications"
static inline void cleanup_free(void *p) { free(*(void**)p); } static notification_id_type notification_id = 0;
#define RAII_ALLOC(type, name, initializer) __attribute__((cleanup(cleanup_free))) type *name = initializer
typedef struct { typedef struct {
notification_id_type next_id; notification_id_type next_id;
@@ -41,10 +38,8 @@ notification_created(DBusMessage *msg, const char* errmsg, void *data) {
uint32_t id; uint32_t id;
if (!glfw_dbus_get_args(msg, "Failed to get Notification uid", DBUS_TYPE_UINT32, &id, DBUS_TYPE_INVALID)) return; if (!glfw_dbus_get_args(msg, "Failed to get Notification uid", DBUS_TYPE_UINT32, &id, DBUS_TYPE_INVALID)) return;
NotificationCreatedData *ncd = (NotificationCreatedData*)data; NotificationCreatedData *ncd = (NotificationCreatedData*)data;
if (ncd) { if (ncd->callback) ncd->callback(ncd->next_id, id, ncd->data);
if (ncd->callback) ncd->callback(ncd->next_id, id, ncd->data); if (data) free(data);
free(ncd);
}
} }
static DBusHandlerResult static DBusHandlerResult
@@ -52,140 +47,59 @@ message_handler(DBusConnection *conn UNUSED, DBusMessage *msg, void *user_data U
/* printf("session_bus message_handler invoked interface: %s member: %s\n", dbus_message_get_interface(msg), dbus_message_get_member(msg)); */ /* printf("session_bus message_handler invoked interface: %s member: %s\n", dbus_message_get_interface(msg), dbus_message_get_member(msg)); */
if (dbus_message_is_signal(msg, NOTIFICATIONS_IFACE, "ActionInvoked")) { if (dbus_message_is_signal(msg, NOTIFICATIONS_IFACE, "ActionInvoked")) {
uint32_t id; uint32_t id;
const char *action = NULL; const char *action;
if (glfw_dbus_get_args(msg, "Failed to get args from ActionInvoked notification signal", if (glfw_dbus_get_args(msg, "Failed to get args from ActionInvoked notification signal",
DBUS_TYPE_UINT32, &id, DBUS_TYPE_STRING, &action, DBUS_TYPE_INVALID)) { DBUS_TYPE_UINT32, &id, DBUS_TYPE_STRING, &action, DBUS_TYPE_INVALID)) {
if (activated_handler) { if (activated_handler) {
activated_handler(id, 2, action); activated_handler(id, action);
return DBUS_HANDLER_RESULT_HANDLED; return DBUS_HANDLER_RESULT_HANDLED;
} }
} }
} }
if (dbus_message_is_signal(msg, NOTIFICATIONS_IFACE, "ActivationToken")) {
uint32_t id;
const char *token = NULL;
if (glfw_dbus_get_args(msg, "Failed to get args from ActivationToken notification signal",
DBUS_TYPE_UINT32, &id, DBUS_TYPE_STRING, &token, DBUS_TYPE_INVALID)) {
if (activated_handler) {
activated_handler(id, 1, token);
return DBUS_HANDLER_RESULT_HANDLED;
}
}
}
if (dbus_message_is_signal(msg, NOTIFICATIONS_IFACE, "NotificationClosed")) {
uint32_t id;
if (glfw_dbus_get_args(msg, "Failed to get args from NotificationClosed notification signal",
DBUS_TYPE_UINT32, &id, DBUS_TYPE_INVALID)) {
if (activated_handler) {
activated_handler(id, 0, "");
return DBUS_HANDLER_RESULT_HANDLED;
}
}
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
} }
static bool
cancel_user_notification(DBusConnection *session_bus, uint32_t *id) {
return glfw_dbus_call_method_no_reply(session_bus, NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE, "CloseNotification", DBUS_TYPE_UINT32, id, DBUS_TYPE_INVALID);
}
static void
got_capabilities(DBusMessage *msg, const char* err, void* data UNUSED) {
if (err) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Notify: Failed to get server capabilities error: %s", err);
return;
}
#define check_call(func, err, ...) if (!func(__VA_ARGS__)) { _glfwInputError(GLFW_PLATFORM_ERROR, "Notify: GetCapabilities: %s", err); return; }
DBusMessageIter iter, array_iter;
check_call(dbus_message_iter_init, "message has no parameters", msg, &iter);
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRING) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Notify: GetCapabilities: %s", "reply is not an array of strings");
return;
}
dbus_message_iter_recurse(&iter, &array_iter);
char buf[2048] = {0}, *p = buf, *end = buf + sizeof(buf);
while (dbus_message_iter_get_arg_type(&array_iter) == DBUS_TYPE_STRING) {
const char *str;
dbus_message_iter_get_basic(&array_iter, &str);
size_t len = strlen(str);
if (len && p + len + 2 < end) { p = stpcpy(p, str); *(p++) = '\n'; }
dbus_message_iter_next(&array_iter);
}
if (activated_handler) activated_handler(0, -1, buf);
#undef check_call
}
static bool
get_capabilities(DBusConnection *session_bus) {
return glfw_dbus_call_method_with_reply(session_bus, NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE, "GetCapabilities", 60, got_capabilities, NULL, DBUS_TYPE_INVALID);
}
notification_id_type notification_id_type
glfw_dbus_send_user_notification(const GLFWDBUSNotificationData *n, GLFWDBusnotificationcreatedfun callback, void *user_data) { glfw_dbus_send_user_notification(const char *app_name, const char* icon, const char *summary, const char *body, const char* action_name, int32_t timeout, GLFWDBusnotificationcreatedfun callback, void *user_data) {
DBusConnection *session_bus = glfw_dbus_session_bus(); DBusConnection *session_bus = glfw_dbus_session_bus();
if (!session_bus) return 0;
if (n->timeout == -9999 && n->urgency == 255) return cancel_user_notification(session_bus, user_data) ? 1 : 0;
if (n->timeout == -99999 && n->urgency == 255) return get_capabilities(session_bus) ? 1 : 0;
static DBusConnection *added_signal_match = NULL; static DBusConnection *added_signal_match = NULL;
if (!session_bus) return 0;
if (added_signal_match != session_bus) { if (added_signal_match != session_bus) {
dbus_bus_add_match(session_bus, "type='signal',interface='" NOTIFICATIONS_IFACE "',member='ActionInvoked'", NULL); dbus_bus_add_match(session_bus, "type='signal',interface='" NOTIFICATIONS_IFACE "',member='ActionInvoked'", NULL);
dbus_bus_add_match(session_bus, "type='signal',interface='" NOTIFICATIONS_IFACE "',member='NotificationClosed'", NULL);
dbus_bus_add_match(session_bus, "type='signal',interface='" NOTIFICATIONS_IFACE "',member='ActivationToken'", NULL);
dbus_connection_add_filter(session_bus, message_handler, NULL, NULL); dbus_connection_add_filter(session_bus, message_handler, NULL, NULL);
added_signal_match = session_bus; added_signal_match = session_bus;
} }
RAII_ALLOC(NotificationCreatedData, data, malloc(sizeof(NotificationCreatedData))); NotificationCreatedData *data = malloc(sizeof(NotificationCreatedData));
if (!data) return 0; if (!data) return 0;
static notification_id_type notification_id = 0;
data->next_id = ++notification_id; data->next_id = ++notification_id;
data->callback = callback; data->data = user_data; data->callback = callback; data->data = user_data;
if (!data->next_id) data->next_id = ++notification_id; if (!data->next_id) data->next_id = ++notification_id;
uint32_t replaces_id = 0;
RAII_MSG(msg, dbus_message_new_method_call(NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE, "Notify")); DBusMessage *msg = dbus_message_new_method_call(NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE, "Notify");
if (!msg) { return 0; } if (!msg) { free(data); return 0; }
DBusMessageIter args, array, variant, dict; DBusMessageIter args, array;
dbus_message_iter_init_append(msg, &args); dbus_message_iter_init_append(msg, &args);
#define check_call(func, ...) if (!func(__VA_ARGS__)) { _glfwInputError(GLFW_PLATFORM_ERROR, "%s", "Out of memory allocating DBUS message for notification\n"); return 0; } #define OOMMSG { free(data); data = NULL; dbus_message_unref(msg); _glfwInputError(GLFW_PLATFORM_ERROR, "%s", "Out of memory allocating DBUS message for notification\n"); return 0; }
#define APPEND(to, type, val) check_call(dbus_message_iter_append_basic, &to, type, &val); #define APPEND(type, val) { if (!dbus_message_iter_append_basic(&args, type, val)) OOMMSG }
APPEND(args, DBUS_TYPE_STRING, n->app_name) APPEND(DBUS_TYPE_STRING, &app_name)
APPEND(args, DBUS_TYPE_UINT32, n->replaces) APPEND(DBUS_TYPE_UINT32, &replaces_id)
APPEND(args, DBUS_TYPE_STRING, n->icon) APPEND(DBUS_TYPE_STRING, &icon)
APPEND(args, DBUS_TYPE_STRING, n->summary) APPEND(DBUS_TYPE_STRING, &summary)
APPEND(args, DBUS_TYPE_STRING, n->body) APPEND(DBUS_TYPE_STRING, &body)
check_call(dbus_message_iter_open_container, &args, DBUS_TYPE_ARRAY, "s", &array); if (!dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY, "s", &array)) OOMMSG;
if (n->actions) { if (action_name) {
for (size_t i = 0; i < n->num_actions; i++) { static const char* default_action = "default";
APPEND(array, DBUS_TYPE_STRING, n->actions[i]); dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &default_action);
} dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &action_name);
} }
check_call(dbus_message_iter_close_container, &args, &array); if (!dbus_message_iter_close_container(&args, &array)) OOMMSG;
check_call(dbus_message_iter_open_container, &args, DBUS_TYPE_ARRAY, "{sv}", &array); if (!dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY, "{sv}", &array)) OOMMSG;
if (!dbus_message_iter_close_container(&args, &array)) OOMMSG;
#define append_sv_dictionary_entry(k, val_type, val) { \ APPEND(DBUS_TYPE_INT32, &timeout)
check_call(dbus_message_iter_open_container, &array, DBUS_TYPE_DICT_ENTRY, NULL, &dict); \ #undef OOMMSG
static const char *key = k; \
APPEND(dict, DBUS_TYPE_STRING, key); \
check_call(dbus_message_iter_open_container, &dict, DBUS_TYPE_VARIANT, val_type##_AS_STRING, &variant); \
APPEND(variant, val_type, val); \
check_call(dbus_message_iter_close_container, &dict, &variant); \
check_call(dbus_message_iter_close_container, &array, &dict); \
}
append_sv_dictionary_entry("urgency", DBUS_TYPE_BYTE, n->urgency);
if (n->category && n->category[0]) append_sv_dictionary_entry("category", DBUS_TYPE_STRING, n->category);
if (n->muted) append_sv_dictionary_entry("suppress-sound", DBUS_TYPE_BOOLEAN, n->muted);
check_call(dbus_message_iter_close_container, &args, &array);
APPEND(args, DBUS_TYPE_INT32, n->timeout)
#undef check_call
#undef APPEND #undef APPEND
if (!call_method_with_msg(session_bus, msg, 5000, notification_created, data, false)) return 0; if (!call_method_with_msg(session_bus, msg, 5000, notification_created, data)) return 0;
notification_id_type ans = data->next_id; return data->next_id;
data = NULL;
return ans;
} }

4
glfw/linux_notify.h vendored
View File

@@ -12,8 +12,8 @@
typedef unsigned long long notification_id_type; typedef unsigned long long notification_id_type;
typedef void (*GLFWDBusnotificationcreatedfun)(notification_id_type, uint32_t, void*); typedef void (*GLFWDBusnotificationcreatedfun)(notification_id_type, uint32_t, void*);
typedef void (*GLFWDBusnotificationactivatedfun)(uint32_t, int, const char*); typedef void (*GLFWDBusnotificationactivatedfun)(uint32_t, const char*);
notification_id_type notification_id_type
glfw_dbus_send_user_notification(const GLFWDBUSNotificationData *n, GLFWDBusnotificationcreatedfun, void*); glfw_dbus_send_user_notification(const char *app_name, const char* icon, const char *summary, const char *body, const char *action_name, int32_t timeout, GLFWDBusnotificationcreatedfun, void*);
void void
glfw_dbus_set_user_notification_activated_handler(GLFWDBusnotificationactivatedfun handler); glfw_dbus_set_user_notification_activated_handler(GLFWDBusnotificationactivatedfun handler);

2
glfw/null_monitor.c vendored
View File

@@ -33,7 +33,7 @@
#include <string.h> #include <string.h>
#include <math.h> #include <math.h>
// The sole (fake) video mode of our (sole) fake monitor // The the sole (fake) video mode of our (sole) fake monitor
// //
static GLFWvidmode getVideoMode(void) static GLFWvidmode getVideoMode(void)
{ {

View File

@@ -13,7 +13,6 @@
"cocoa_joystick.m", "cocoa_joystick.m",
"cocoa_monitor.m", "cocoa_monitor.m",
"cocoa_window.m", "cocoa_window.m",
"cocoa_displaylink.m",
"posix_thread.c", "posix_thread.c",
"nsgl_context.m", "nsgl_context.m",
"egl_context.c", "egl_context.c",
@@ -57,6 +56,7 @@
"posix_thread.h", "posix_thread.h",
"wl_cursors.h", "wl_cursors.h",
"wl_text_input.h", "wl_text_input.h",
"wl_decor.h",
"xkb_glfw.h", "xkb_glfw.h",
"dbus_glfw.h", "dbus_glfw.h",
"ibus_glfw.h", "ibus_glfw.h",
@@ -82,10 +82,6 @@
"unstable/tablet/tablet-unstable-v2.xml", "unstable/tablet/tablet-unstable-v2.xml",
"staging/cursor-shape/cursor-shape-v1.xml", "staging/cursor-shape/cursor-shape-v1.xml",
"staging/fractional-scale/fractional-scale-v1.xml", "staging/fractional-scale/fractional-scale-v1.xml",
"staging/single-pixel-buffer/single-pixel-buffer-v1.xml",
"unstable/idle-inhibit/idle-inhibit-unstable-v1.xml",
"staging/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml",
"staging/xdg-system-bell/xdg-system-bell-v1.xml",
"kwin-blur-v1.xml", "kwin-blur-v1.xml",
"wlr-layer-shell-unstable-v1.xml" "wlr-layer-shell-unstable-v1.xml"
@@ -95,6 +91,7 @@
"wl_monitor.c", "wl_monitor.c",
"wl_window.c", "wl_window.c",
"wl_cursors.c", "wl_cursors.c",
"wl_decor.c",
"wl_text_input.c", "wl_text_input.c",
"wl_client_side_decorations.c", "wl_client_side_decorations.c",
"posix_thread.c", "posix_thread.c",
@@ -148,7 +145,6 @@
"linux_joystick.h", "linux_joystick.h",
"null_joystick.h", "null_joystick.h",
"linux_notify.h", "linux_notify.h",
"linux_desktop_settings.h",
"main_loop.h" "main_loop.h"
], ],
"sources": [ "sources": [
@@ -165,7 +161,6 @@
"backend_utils.c", "backend_utils.c",
"linux_joystick.c", "linux_joystick.c",
"null_joystick.c", "null_joystick.c",
"linux_desktop_settings.c",
"linux_notify.c" "linux_notify.c"
] ]
} }

View File

@@ -13,8 +13,8 @@
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#define debug(...) if (_glfw.hints.init.debugRendering) fprintf(stderr, __VA_ARGS__);
#define decs window->wl.decorations #define decs window->wl.decorations
#define debug debug_rendering
#define ARGB(a, r, g, b) (((a) << 24) | ((r) << 16) | ((g) << 8) | (b)) #define ARGB(a, r, g, b) (((a) << 24) | ((r) << 16) | ((g) << 8) | (b))
#define A(x) (((x) >> 24) & 0xff) #define A(x) (((x) >> 24) & 0xff)
@@ -23,7 +23,8 @@
#define B(x) ((x) & 0xff) #define B(x) ((x) & 0xff)
#define SWAP(x, y) do { __typeof__(x) SWAP = x; x = y; y = SWAP; } while (0) #define SWAP(x, y) do { __typeof__(x) SWAP = x; x = y; y = SWAP; } while (0)
// shadow tile {{{ static const uint32_t passive_bg_color = 0xffeeeeee;
static const uint32_t active_bg_color = 0xffdddad6;
typedef float kernel_type; typedef float kernel_type;
static void static void
@@ -85,29 +86,6 @@ create_shadow_mask(size_t width, size_t height, size_t margin, size_t kernel_siz
return mask; return mask;
} }
#define st decs.shadow_tile
static size_t
create_shadow_tile(_GLFWwindow *window) {
const size_t margin = (size_t)round(decs.metrics.width * decs.for_window_state.fscale);
if (st.data && st.for_decoration_size == margin) return margin;
st.for_decoration_size = margin;
free(st.data);
st.segments = 7;
st.stride = st.segments * margin;
st.corner_size = margin * (st.segments - 1) / 2;
kernel_type* mask = create_shadow_mask(st.stride, st.stride, margin, 2 * margin + 1, (kernel_type)0.7, 32 * margin);
st.data = malloc(sizeof(uint32_t) * st.stride * st.stride);
if (st.data) for (size_t i = 0; i < st.stride * st.stride; i++) st.data[i] = ((uint8_t)(mask[i] * 255)) << 24;
free(mask);
return margin;
}
// }}}
static bool window_needs_shadows(_GLFWwindow *w) { return !(w->wl.current.toplevel_states & TOPLEVEL_STATE_DOCKED); }
static void static void
swap_buffers(_GLFWWaylandBufferPair *pair) { swap_buffers(_GLFWWaylandBufferPair *pair) {
SWAP(pair->front, pair->back); SWAP(pair->front, pair->back);
@@ -115,24 +93,19 @@ swap_buffers(_GLFWWaylandBufferPair *pair) {
} }
static size_t static size_t
init_buffer_pair(_GLFWWaylandBufferPair *pair, size_t width, size_t height, double scale) { init_buffer_pair(_GLFWWaylandBufferPair *pair, size_t width, size_t height, unsigned scale) {
memset(pair, 0, sizeof(_GLFWWaylandBufferPair)); memset(pair, 0, sizeof(_GLFWWaylandBufferPair));
pair->width = (int)round(width * scale); pair->width = width * scale;
pair->height = (int)round(height * scale); pair->height = height * scale;
pair->viewport_width = width; pair->viewport_height = height;
pair->stride = 4 * pair->width; pair->stride = 4 * pair->width;
pair->size_in_bytes = pair->stride * pair->height; pair->size_in_bytes = pair->stride * pair->height;
return 2 * pair->size_in_bytes; return 2 * pair->size_in_bytes;
} }
#define all_shadow_surfaces(Q) Q(shadow_left); Q(shadow_top); Q(shadow_right); Q(shadow_bottom); \
Q(shadow_upper_left); Q(shadow_upper_right); Q(shadow_lower_left); Q(shadow_lower_right);
#define all_surfaces(Q) Q(titlebar); all_shadow_surfaces(Q);
static bool static bool
window_has_buffer(_GLFWwindow *window, struct wl_buffer *q) { window_has_buffer(_GLFWwindow *window, struct wl_buffer *q) {
#define Q(which) if (decs.which.buffer.a == q) { decs.which.buffer.a_needs_to_be_destroyed = false; return true; } if (decs.which.buffer.b == q) { decs.which.buffer.b_needs_to_be_destroyed = false; return true; } #define Q(which) if (decs.which.buffer.a == q) { decs.which.buffer.a_needs_to_be_destroyed = false; return true; } if (decs.which.buffer.b == q) { decs.which.buffer.b_needs_to_be_destroyed = false; return true; }
all_surfaces(Q); Q(left); Q(top); Q(right); Q(bottom);
#undef Q #undef Q
return false; return false;
} }
@@ -162,305 +135,146 @@ alloc_buffer_pair(uintptr_t window_id, _GLFWWaylandBufferPair *pair, struct wl_s
pair->data.front = pair->data.a; pair->data.back = pair->data.b; pair->data.front = pair->data.a; pair->data.back = pair->data.b;
} }
#define st decs.shadow_tile
void void
csd_initialize_metrics(_GLFWwindow *window) { initialize_csd_metrics(_GLFWwindow *window) {
decs.metrics.width = 12; decs.metrics.width = 12;
decs.metrics.top = 36; decs.metrics.top = 36;
decs.metrics.visible_titlebar_height = decs.metrics.top - decs.metrics.width; decs.metrics.visible_titlebar_height = window->wl.decorations.metrics.top - window->wl.decorations.metrics.width;
decs.metrics.horizontal = 2 * decs.metrics.width; decs.metrics.horizontal = 2 * window->wl.decorations.metrics.width;
decs.metrics.vertical = decs.metrics.width + decs.metrics.top; decs.metrics.vertical = window->wl.decorations.metrics.width + window->wl.decorations.metrics.top;
} }
static void static size_t
patch_titlebar_with_alpha_mask(uint32_t *dest, uint8_t *src, unsigned height, unsigned dest_stride, unsigned src_width, unsigned dest_left, uint32_t bg, uint32_t fg) { create_shadow_tile(_GLFWwindow *window) {
for (unsigned y = 0; y < height; y++, src += src_width, dest += dest_stride) { const size_t margin = decs.bottom.buffer.height;
uint32_t *d = dest + dest_left; if (st.data && st.for_decoration_size == margin) return margin;
for (unsigned i = 0; i < src_width; i++) { st.for_decoration_size = margin;
const uint8_t alpha = src[i], calpha = 255 - alpha; free(st.data);
// Blend the red and blue components st.segments = 7;
uint32_t ans = ((bg & 0xff00ff) * calpha + (fg & 0xff00ff) * alpha) & 0xff00ff00; st.stride = st.segments * margin;
// Blend the green component st.corner_size = margin * (st.segments - 1) / 2;
ans += ((bg & 0xff00) * calpha + (fg & 0xff00) * alpha) & 0xff0000; kernel_type* mask = create_shadow_mask(st.stride, st.stride, margin, 2 * margin + 1, (kernel_type)0.7, 32 * margin);
ans >>= 8; st.data = malloc(sizeof(uint32_t) * st.stride * st.stride);
d[i] = ans | 0xff000000; if (st.data) for (size_t i = 0; i < st.stride * st.stride; i++) st.data[i] = ((uint8_t)(mask[i] * 255)) << 24;
} free(mask);
} return margin;
} }
static void
render_hline(uint8_t *out, unsigned width, unsigned thickness, unsigned bottom, unsigned left, unsigned right) {
for (unsigned y = bottom - thickness; y < bottom; y++) {
uint8_t *dest = out + width * y;
for (unsigned x = left; x < right; x++) dest[x] = 255;
}
}
static void
render_vline(uint8_t *out, unsigned width, unsigned thickness, unsigned left, unsigned top, unsigned bottom) {
for (unsigned y = top; y < bottom; y++) {
uint8_t *dest = out + width * y;
for (unsigned x = left; x < left + thickness; x++) dest[x] = 255;
}
}
static int
scale(unsigned thickness, float factor) {
return (unsigned)(roundf(thickness * factor));
}
static void
render_minimize(uint8_t *out, unsigned width, unsigned height) {
memset(out, 0, width * height);
unsigned thickness = height / 12;
unsigned baseline = height - thickness * 2;
unsigned side_margin = scale(thickness, 3.8f);
if (!thickness || width <= side_margin || height < baseline + 2 * thickness) return;
render_hline(out, width, thickness, baseline, side_margin, width - side_margin);
}
static void
render_maximize(uint8_t *out, unsigned width, unsigned height) {
memset(out, 0, width * height);
unsigned thickness = height / 12, half_thickness = thickness / 2;
unsigned baseline = height - thickness * 2;
unsigned side_margin = scale(thickness, 3.0f);
unsigned top = 4 * thickness;
if (!half_thickness || width <= side_margin || height < baseline + 2 * thickness || top >= baseline) return;
render_hline(out, width, half_thickness, baseline, side_margin, width - side_margin);
render_hline(out, width, thickness, top + thickness, side_margin, width - side_margin);
render_vline(out, width, half_thickness, side_margin, top, baseline);
render_vline(out, width, half_thickness, width - side_margin, top, baseline);
}
static void
render_restore(uint8_t *out, unsigned width, unsigned height) {
memset(out, 0, width * height);
unsigned thickness = height / 12, half_thickness = thickness / 2;
unsigned baseline = height - thickness * 2;
unsigned side_margin = scale(thickness, 3.0f);
unsigned top = 4 * thickness;
if (!half_thickness || width <= side_margin || height < baseline + 2 * thickness || top >= baseline) return;
unsigned box_height = ((baseline - top) * 3) / 4;
if (box_height < 2*thickness) return;
unsigned box_width = ((width - 2 * side_margin) * 3) / 4;
// bottom box
unsigned box_top = baseline - box_height, left = side_margin, right = side_margin + box_width, bottom = baseline;
render_hline(out, width, thickness, box_top + thickness, left, right);
render_hline(out, width, half_thickness, bottom, left, right);
render_vline(out, width, half_thickness, left, box_top, bottom);
render_vline(out, width, half_thickness, side_margin + box_width, baseline - box_height, baseline);
// top box
unsigned box_x_shift = 2 * thickness, box_y_shift = 2 * thickness;
box_x_shift = MIN(width - right, box_x_shift);
box_y_shift = MIN(box_top, box_y_shift);
unsigned left2 = left + box_x_shift, right2 = right + box_x_shift, top2 = box_top - box_y_shift, bottom2 = bottom - box_y_shift;
render_hline(out, width, thickness, top2 + thickness, left2, right2);
render_vline(out, width, half_thickness, right2, top2, bottom2);
render_hline(out, width, half_thickness, bottom2, right, right2);
render_vline(out, width, half_thickness, left2, top2, box_top);
}
static void
render_line(uint8_t *buf, unsigned width, unsigned height, unsigned thickness, int x1, int y1, int x2, int y2) {
float m = (y2 - y1) / (float)(x2 - x1);
float c = y1 - m * x1;
unsigned delta = thickness / 2, extra = thickness % 2;
for (int x = MAX(0, MIN(x1, x2)); x < MIN((int)width, MAX(x1, x2) + 1); x++) {
float ly = m * x + c;
for (int y = MAX(0, (int)(ly - delta)); y < MIN((int)height, (int)(ly + delta + extra + 1)); y++) buf[x + y * width] = 255;
}
for (int y = MAX(0, MIN(y1, y2)); y < MIN((int)height, MAX(y1, y2) + 1); y++) {
float lx = (y - c) / m;
for (int x = MAX(0, (int)(lx - delta)); x < MIN((int)width, (int)(lx + delta + extra + 1)); x++) buf[x + y * width] = 255;
}
}
static void
render_close(uint8_t *out, unsigned width, unsigned height) {
memset(out, 0, width * height);
unsigned thickness = height / 12;
unsigned baseline = height - thickness * 2;
unsigned side_margin = scale(thickness, 3.3f);
int top = baseline - (width - 2 * side_margin);
if (top <= 0) return;
unsigned line_thickness = scale(thickness, 1.5f);
render_line(out, width, height, line_thickness, side_margin, top, width - side_margin, baseline);
render_line(out, width, height, line_thickness, side_margin, baseline, width - side_margin, top);
}
static uint32_t
average_intensity_in_src(uint8_t *src, unsigned src_width, unsigned src_x, unsigned src_y, unsigned factor) {
uint32_t ans = 0;
for (unsigned y = src_y; y < src_y + factor; y++) {
uint8_t *s = src + src_width * y;
for (unsigned x = src_x; x < src_x + factor; x++) ans += s[x];
}
return ans / (factor * factor);
}
static void
downsample(uint8_t *dest, uint8_t *src, unsigned dest_width, unsigned dest_height, unsigned factor) {
unsigned src_width = factor * dest_width;
for (unsigned y = 0; y < dest_height; y++) {
uint8_t *d = dest + dest_width * y;
for (unsigned x = 0; x < dest_width; x++) {
d[x] = MIN(255u, (uint32_t)d[x] + average_intensity_in_src(src, src_width, x * factor, y * factor, factor));
}
}
}
static void
render_button(void(*which)(uint8_t *, unsigned, unsigned), bool antialias, uint32_t *dest, uint8_t *src, unsigned height, unsigned dest_stride, unsigned src_width, unsigned dest_left, uint32_t bg, uint32_t fg) {
if (antialias) {
static const unsigned factor = 4;
uint8_t *big_src = malloc(factor * factor * height * src_width);
if (big_src) {
which(big_src, src_width * factor, height * factor);
memset(src, 0, src_width * height);
downsample(src, big_src, src_width, height, factor);
free(big_src);
} else which(src, src_width, height);
} else which(src, src_width, height);
patch_titlebar_with_alpha_mask(dest, src, height, dest_stride, src_width, dest_left, bg, fg);
}
static void static void
render_title_bar(_GLFWwindow *window, bool to_front_buffer) { render_title_bar(_GLFWwindow *window, bool to_front_buffer) {
const bool is_focused = window->id == _glfw.focusedWindowId; const bool is_focused = window->id == _glfw.focusedWindowId;
const bool is_maximized = window->wl.current.toplevel_states & TOPLEVEL_STATE_MAXIMIZED; uint32_t bg_color = is_focused ? active_bg_color : passive_bg_color;
const uint32_t light_fg = is_focused ? 0xff444444 : 0xff888888, light_bg = is_focused ? 0xffdddad6 : 0xffeeeeee; uint32_t fg_color = is_focused ? 0xff444444 : 0xff888888;
const uint32_t dark_fg = is_focused ? 0xffffffff : 0xffcccccc, dark_bg = is_focused ? 0xff303030 : 0xff242424; if (decs.use_custom_titlebar_color) {
static const uint32_t hover_dark_bg = 0xff444444, hover_light_bg = 0xffbbbbbb;
uint32_t bg_color = light_bg, fg_color = light_fg, hover_bg = hover_light_bg;
GLFWColorScheme appearance = glfwGetCurrentSystemColorTheme(false);
bool is_dark = false;
if (decs.use_custom_titlebar_color || appearance == GLFW_COLOR_SCHEME_NO_PREFERENCE) {
bg_color = 0xff000000 | (decs.titlebar_color & 0xffffff); bg_color = 0xff000000 | (decs.titlebar_color & 0xffffff);
double red = ((bg_color >> 16) & 0xFF) / 255.0; double red = ((bg_color >> 16) & 0xFF) / 255.0;
double green = ((bg_color >> 8) & 0xFF) / 255.0; double green = ((bg_color >> 8) & 0xFF) / 255.0;
double blue = (bg_color & 0xFF) / 255.0; double blue = (bg_color & 0xFF) / 255.0;
double luma = 0.2126 * red + 0.7152 * green + 0.0722 * blue; double luma = 0.2126 * red + 0.7152 * green + 0.0722 * blue;
if (luma < 0.5) { fg_color = dark_fg; hover_bg = hover_dark_bg; is_dark = true; } if (luma < 0.5) {
if (!decs.use_custom_titlebar_color) bg_color = luma < 0.5 ? dark_bg : light_bg; fg_color = is_focused ? 0xffeeeeee : 0xff888888;
} else if (appearance == GLFW_COLOR_SCHEME_DARK) { bg_color = dark_bg; fg_color = dark_fg; hover_bg = hover_dark_bg; is_dark = true; } }
uint8_t *output = to_front_buffer ? decs.titlebar.buffer.data.front : decs.titlebar.buffer.data.back; }
uint8_t *output = to_front_buffer ? decs.top.buffer.data.front : decs.top.buffer.data.back;
// render shadow part
const size_t margin = create_shadow_tile(window);
const size_t edge_segment_size = st.corner_size - margin;
const uint8_t divisor = is_focused ? 1 : 2;
for (size_t y = 0; y < margin; y++) {
// left segment
uint32_t *s = st.data + y * st.stride + margin;
uint32_t *d = (uint32_t*)(output + y * decs.top.buffer.stride);
for (size_t x = 0; x < edge_segment_size; x++) d[x] = (A(s[x]) / divisor) << 24;
// middle segment
s += edge_segment_size;
size_t limit = decs.top.buffer.width > edge_segment_size ? decs.top.buffer.width - edge_segment_size : 0;
for (size_t x = edge_segment_size, sx = 0; x < limit; x++, sx = (sx + 1) % margin) d[x] = (A(s[sx]) / divisor) << 24;
// right segment
s += margin;
for (size_t x = limit; x < decs.top.buffer.width; x++, s++) d[x] = (A(*s) / divisor) << 24;
}
// render text part // render text part
int button_size = decs.titlebar.buffer.height; output += decs.top.buffer.stride * margin;
int num_buttons = 1;
if (window->wl.wm_capabilities.maximize) num_buttons++;
if (window->wl.wm_capabilities.minimize) num_buttons++;
if (window->wl.title && window->wl.title[0] && _glfw.callbacks.draw_text) { if (window->wl.title && window->wl.title[0] && _glfw.callbacks.draw_text) {
if (_glfw.callbacks.draw_text((GLFWwindow*)window, window->wl.title, fg_color, bg_color, output, decs.titlebar.buffer.width, decs.titlebar.buffer.height, 0, 0, num_buttons * button_size, false)) goto render_buttons; if (_glfw.callbacks.draw_text((GLFWwindow*)window, window->wl.title, fg_color, bg_color, output, decs.top.buffer.width, decs.top.buffer.height - margin, 0, 0, 0)) return;
} }
// rendering of text failed, blank the buffer for (uint32_t *px = (uint32_t*)output, *end = (uint32_t*)(output + decs.top.buffer.size_in_bytes); px < end; px++) {
for (uint32_t *px = (uint32_t*)output, *end = (uint32_t*)(output + decs.titlebar.buffer.size_in_bytes); px < end; px++) *px = bg_color; *px = bg_color;
render_buttons:
decs.maximize.width = 0; decs.minimize.width = 0; decs.close.width = 0;
if (!button_size) return;
uint8_t *alpha_mask = malloc(button_size * button_size);
int left = decs.titlebar.buffer.width - num_buttons * button_size;
if (!alpha_mask || left <= 0) return;
#define drawb(which, antialias, func, hover_bg) { \
render_button(func, antialias, (uint32_t*)output, alpha_mask, button_size, decs.titlebar.buffer.width, button_size, left, decs.which.hovered ? hover_bg : bg_color, fg_color); decs.which.left = left; decs.which.width = button_size; left += button_size; }
if (window->wl.wm_capabilities.minimize) drawb(minimize, false, render_minimize, hover_bg);
if (window->wl.wm_capabilities.maximize) {
if (is_maximized) { drawb(maximize, false, render_restore, hover_bg); } else { drawb(maximize, false, render_maximize, hover_bg); }
} }
drawb(close, true, render_close, is_dark ? 0xff880000: 0xffc80000);
free(alpha_mask);
#undef drawb
} }
static void static void
update_title_bar(_GLFWwindow *window) { update_title_bar(_GLFWwindow *window) {
render_title_bar(window, false); render_title_bar(window, false);
swap_buffers(&decs.titlebar.buffer); swap_buffers(&decs.top.buffer);
} }
static void static void
render_horizontal_shadow(_GLFWwindow *window, ssize_t scaled_shadow_size, ssize_t src_y_offset, ssize_t y, _GLFWWaylandBufferPair *buf) { render_edges(_GLFWwindow *window) {
// left region const size_t margin = create_shadow_tile(window);
ssize_t src_y = src_y_offset + y; if (!st.data) return; // out of memory
const ssize_t src_leftover_corner = st.corner_size - scaled_shadow_size;
uint32_t *src = st.data + st.stride * src_y + scaled_shadow_size;
uint32_t *d_start = (uint32_t*)(buf->data.front + y * buf->stride);
uint32_t *d_end = (uint32_t*)(buf->data.front + (y+1) * buf->stride);
uint32_t *left_region_end = d_start + MIN(d_end - d_start, src_leftover_corner);
memcpy(d_start, src, sizeof(uint32_t) * (left_region_end - d_start));
// right region
uint32_t *right_region_start = MAX(d_start, d_end - src_leftover_corner);
src = st.data + st.stride * (src_y+1) - st.corner_size;
memcpy(right_region_start, src, sizeof(uint32_t) * MIN(src_leftover_corner, d_end - right_region_start));
src = st.data + st.stride * src_y + st.corner_size;
// middle region
for (uint32_t *d = left_region_end; d < right_region_start; d += scaled_shadow_size)
memcpy(d, src, sizeof(uint32_t) * MIN(right_region_start - d, scaled_shadow_size));
}
static void // bottom edge
copy_vertical_region( uint32_t *src = st.data + (st.segments - 1) * margin * st.stride;
_GLFWwindow *window, ssize_t src_y_start, ssize_t src_y_limit, for (size_t y = 0; y < margin; y++) {
ssize_t y_start, ssize_t y_limit, ssize_t src_x_offset, _GLFWWaylandBufferPair *buf uint32_t *d = (uint32_t*)(decs.bottom.buffer.data.front + y * decs.bottom.buffer.stride);
) { uint32_t *s = src + st.stride * y;
for (ssize_t dy = y_start, sy = src_y_start; dy < y_limit && sy < src_y_limit; dy++, sy++) // left corner
memcpy(buf->data.front + dy * buf->stride, st.data + sy * st.stride + src_x_offset, sizeof(uint32_t) * buf->width); for (size_t x = 0; x < st.corner_size && x < decs.bottom.buffer.width; x++) d[x] = s[x];
} // middle
size_t pos = st.corner_size, limit = decs.bottom.buffer.width > st.corner_size ? decs.bottom.buffer.width - st.corner_size : 0;
static void s += st.corner_size;
render_shadows(_GLFWwindow *window) { while (pos < limit) {
if (!window_needs_shadows(window)) return; uint32_t *p = d + pos;
const ssize_t scaled_shadow_size = create_shadow_tile(window); for (size_t x = 0; x < margin && pos + x < limit; x++) p[x] = s[x];
if (!st.data || !scaled_shadow_size) return; // out of memory pos += margin;
// upper and lower shadows }
for (ssize_t y = 0; y < scaled_shadow_size; y++) { // right corner
_GLFWWaylandBufferPair *buf = &decs.shadow_upper_left.buffer; s += margin;
uint32_t *src = st.data + st.stride * y; for (size_t x = 0; x < st.corner_size && limit + x < decs.bottom.buffer.width; x++) d[limit + x] = s[x];
uint32_t *d = (uint32_t*)(buf->data.front + y * buf->stride); }
memcpy(d, src, sizeof(uint32_t) * scaled_shadow_size);
// upper corners
buf = &decs.shadow_upper_right.buffer; for (size_t y = 0; y < st.corner_size && y < decs.left.buffer.height; y++) {
src += st.stride - scaled_shadow_size; uint32_t *left_src = st.data + st.stride * y;
d = (uint32_t*)(buf->data.front + y * buf->stride); uint32_t *left_dest = (uint32_t*)(decs.left.buffer.data.front + y * decs.left.buffer.stride);
memcpy(d, src, sizeof(uint32_t) * scaled_shadow_size); memcpy(left_dest, left_src, margin * sizeof(uint32_t));
uint32_t *right_src = left_src + 2 * st.corner_size;
const size_t tile_bottom_start = st.stride - scaled_shadow_size; uint32_t *right_dest = (uint32_t*)(decs.right.buffer.data.front + y * decs.right.buffer.stride);
buf = &decs.shadow_lower_left.buffer; memcpy(right_dest, right_src, margin * sizeof(uint32_t));
src = st.data + (tile_bottom_start + y) * st.stride; }
d = (uint32_t*)(buf->data.front + y * buf->stride);
memcpy(d, src, sizeof(uint32_t) * scaled_shadow_size); // lower corners
size_t src_height = st.corner_size - margin;
buf = &decs.shadow_lower_right.buffer; size_t dest_top = decs.left.buffer.height > src_height ? decs.left.buffer.height - src_height : 0;
src += st.stride - scaled_shadow_size; size_t src_top = st.corner_size + margin;
d = (uint32_t*)(buf->data.front + y * buf->stride); for (size_t src_y = src_top, dest_y = dest_top; src_y < src_top + src_height && dest_y < decs.left.buffer.height; src_y++, dest_y++) {
memcpy(d, src, sizeof(uint32_t) * scaled_shadow_size); uint32_t *s = st.data + st.stride * src_y;
uint32_t *d = (uint32_t*)(decs.left.buffer.data.front + dest_y * decs.left.buffer.stride);
render_horizontal_shadow(window, scaled_shadow_size, 0, y, &decs.shadow_top.buffer); memcpy(d, s, margin * sizeof(uint32_t));
render_horizontal_shadow(window, scaled_shadow_size, st.stride - scaled_shadow_size, y, &decs.shadow_bottom.buffer); s += 2 * st.corner_size;
d = (uint32_t*)(decs.right.buffer.data.front + dest_y * decs.left.buffer.stride);
memcpy(d, s, margin * sizeof(uint32_t));
}
// sides
size_t limit = decs.left.buffer.height > src_height ? decs.left.buffer.height - src_height : 0;
for (size_t dest_y = st.corner_size, src_y = 0; dest_y < limit; dest_y++, src_y = (src_y + 1) % margin) {
uint32_t *src = st.data + (st.corner_size + src_y) * st.stride;
uint32_t *left_dest = (uint32_t*)(decs.left.buffer.data.front + dest_y * decs.left.buffer.stride);
memcpy(left_dest, src, margin * sizeof(uint32_t));
src += 2 * st.corner_size;
uint32_t *right_dest = (uint32_t*)(decs.right.buffer.data.front + dest_y * decs.right.buffer.stride);
memcpy(right_dest, src, margin * sizeof(uint32_t));
} }
// side shadows
// top region
const ssize_t src_leftover_corner = st.corner_size - scaled_shadow_size;
ssize_t y_start = 0, y_end = decs.shadow_left.buffer.height, top_end = MIN(y_end, src_leftover_corner);
ssize_t right_src_start = st.stride - scaled_shadow_size;
#define c(src_y_start, src_y_limit, dest_y_start, dest_y_limit) { \
copy_vertical_region(window, src_y_start, src_y_limit, dest_y_start, dest_y_limit, 0, &decs.shadow_left.buffer); \
copy_vertical_region(window, src_y_start, src_y_limit, dest_y_start, dest_y_limit, right_src_start, &decs.shadow_right.buffer); \
}
c(scaled_shadow_size, st.corner_size, y_start, top_end);
// bottom region
ssize_t bottom_start = MAX(0, y_end - src_leftover_corner);
c(st.stride - st.corner_size, st.stride - scaled_shadow_size, bottom_start, y_end);
// middle region
for (ssize_t dest_y = top_end; dest_y < bottom_start; dest_y += scaled_shadow_size)
c(st.corner_size, st.corner_size + scaled_shadow_size, dest_y, MIN(dest_y + scaled_shadow_size, bottom_start));
#undef c
#define copy(which) for (uint32_t *src = (uint32_t*)decs.which.buffer.data.front, *dest = (uint32_t*)decs.which.buffer.data.back; src < (uint32_t*)(decs.which.buffer.data.front + decs.which.buffer.size_in_bytes); src++, dest++) *dest = (A(*src) / 2 ) << 24; #define copy(which) for (uint32_t *src = (uint32_t*)decs.which.buffer.data.front, *dest = (uint32_t*)decs.which.buffer.data.back; src < (uint32_t*)(decs.which.buffer.data.front + decs.which.buffer.size_in_bytes); src++, dest++) *dest = (A(*src) / 2 ) << 24;
all_shadow_surfaces(copy); copy(left); copy(bottom); copy(right);
#undef copy #undef copy
} }
@@ -468,18 +282,16 @@ render_shadows(_GLFWwindow *window) {
static bool static bool
create_shm_buffers(_GLFWwindow* window) { create_shm_buffers(_GLFWwindow* window) {
const unsigned scale = _glfwWaylandIntegerWindowScale(window);
const size_t vertical_width = decs.metrics.width, vertical_height = window->wl.height + decs.metrics.top;
const size_t horizontal_height = decs.metrics.width, horizontal_width = window->wl.width + 2 * decs.metrics.width;
decs.mapping.size = 0; decs.mapping.size = 0;
#define bp(which, width, height) decs.mapping.size += init_buffer_pair(&decs.which.buffer, width, height, decs.for_window_state.fscale); decs.mapping.size += init_buffer_pair(&decs.top.buffer, window->wl.width, decs.metrics.top, scale);
bp(titlebar, window->wl.width, decs.metrics.visible_titlebar_height); decs.mapping.size += init_buffer_pair(&decs.left.buffer, vertical_width, vertical_height, scale);
bp(shadow_top, window->wl.width, decs.metrics.width); decs.mapping.size += init_buffer_pair(&decs.bottom.buffer, horizontal_width, horizontal_height, scale);
bp(shadow_bottom, window->wl.width, decs.metrics.width); decs.mapping.size += init_buffer_pair(&decs.right.buffer, vertical_width, vertical_height, scale);
bp(shadow_left, decs.metrics.width, window->wl.height + decs.metrics.visible_titlebar_height);
bp(shadow_right, decs.metrics.width, window->wl.height + decs.metrics.visible_titlebar_height);
bp(shadow_upper_left, decs.metrics.width, decs.metrics.width);
bp(shadow_upper_right, decs.metrics.width, decs.metrics.width);
bp(shadow_lower_left, decs.metrics.width, decs.metrics.width);
bp(shadow_lower_right, decs.metrics.width, decs.metrics.width);
#undef bp
int fd = createAnonymousFile(decs.mapping.size); int fd = createAnonymousFile(decs.mapping.size);
if (fd < 0) { if (fd < 0) {
@@ -496,170 +308,137 @@ create_shm_buffers(_GLFWwindow* window) {
struct wl_shm_pool* pool = wl_shm_create_pool(_glfw.wl.shm, fd, decs.mapping.size); struct wl_shm_pool* pool = wl_shm_create_pool(_glfw.wl.shm, fd, decs.mapping.size);
close(fd); close(fd);
size_t offset = 0; size_t offset = 0;
#define Q(which) alloc_buffer_pair(window->id, &decs.which.buffer, pool, decs.mapping.data, &offset) #define a(which) alloc_buffer_pair(window->id, &decs.which.buffer, pool, decs.mapping.data, &offset)
all_surfaces(Q); a(top); a(left); a(bottom); a(right);
#undef Q #undef a
wl_shm_pool_destroy(pool); wl_shm_pool_destroy(pool);
create_shadow_tile(window);
render_title_bar(window, true); render_title_bar(window, true);
render_shadows(window); render_edges(window);
debug("Created decoration buffers at scale: %f\n", decs.for_window_state.fscale); debug("Created decoration buffers at scale: %u vertical_height: %zu horizontal_width: %zu\n", scale, vertical_height, horizontal_width);
return true; return true;
} }
static void void
free_csd_surface(_GLFWWaylandCSDSurface *s) {
if (s->subsurface) wl_subsurface_destroy(s->subsurface);
s->subsurface = NULL;
if (s->wp_viewport) wp_viewport_destroy(s->wp_viewport);
s->wp_viewport = NULL;
if (s->surface) wl_surface_destroy(s->surface);
s->surface = NULL;
}
static void
free_csd_surfaces(_GLFWwindow *window) { free_csd_surfaces(_GLFWwindow *window) {
#define Q(which) free_csd_surface(&decs.which) #define d(which) {\
all_surfaces(Q); if (decs.which.subsurface) wl_subsurface_destroy(decs.which.subsurface); \
#undef Q decs.which.subsurface = NULL; \
if (decs.which.surface) wl_surface_destroy(decs.which.surface); \
decs.which.surface = NULL; \
}
d(left); d(top); d(right); d(bottom);
#undef d
} }
static void static void
free_csd_buffers(_GLFWwindow *window) { free_csd_buffers(_GLFWwindow *window) {
#define Q(which) { \ #define d(which) { \
if (decs.which.buffer.a_needs_to_be_destroyed && decs.which.buffer.a) wl_buffer_destroy(decs.which.buffer.a); \ if (decs.which.buffer.a_needs_to_be_destroyed && decs.which.buffer.a) wl_buffer_destroy(decs.which.buffer.a); \
if (decs.which.buffer.b_needs_to_be_destroyed && decs.which.buffer.b) wl_buffer_destroy(decs.which.buffer.b); \ if (decs.which.buffer.b_needs_to_be_destroyed && decs.which.buffer.b) wl_buffer_destroy(decs.which.buffer.b); \
memset(&decs.which.buffer, 0, sizeof(_GLFWWaylandBufferPair)); \ memset(&decs.which.buffer, 0, sizeof(_GLFWWaylandBufferPair)); \
} }
all_surfaces(Q); d(left); d(top); d(right); d(bottom);
#undef Q #undef d
if (decs.mapping.data) munmap(decs.mapping.data, decs.mapping.size); if (decs.mapping.data) munmap(decs.mapping.data, decs.mapping.size);
decs.mapping.data = NULL; decs.mapping.size = 0; decs.mapping.data = NULL; decs.mapping.size = 0;
} }
static void static void
position_csd_surface(_GLFWWaylandCSDSurface *s, int x, int y) { position_csd_surface(_GLFWWaylandCSDEdge *s, int x, int y, int scale) {
if (s->surface) { wl_surface_set_buffer_scale(s->surface, scale);
wl_surface_set_buffer_scale(s->surface, 1); s->x = x; s->y = y;
s->x = x; s->y = y; wl_subsurface_set_position(s->subsurface, s->x, s->y);
wl_subsurface_set_position(s->subsurface, s->x, s->y);
}
} }
static void static void
create_csd_surfaces(_GLFWwindow *window, _GLFWWaylandCSDSurface *s) { create_csd_surfaces(_GLFWwindow *window, _GLFWWaylandCSDEdge *s) {
if (s->surface) wl_surface_destroy(s->surface);
s->surface = wl_compositor_create_surface(_glfw.wl.compositor); s->surface = wl_compositor_create_surface(_glfw.wl.compositor);
wl_surface_set_user_data(s->surface, window);
if (s->subsurface) wl_subsurface_destroy(s->subsurface);
s->subsurface = wl_subcompositor_get_subsurface(_glfw.wl.subcompositor, s->surface, window->wl.surface); s->subsurface = wl_subcompositor_get_subsurface(_glfw.wl.subcompositor, s->surface, window->wl.surface);
if (_glfw.wl.wp_viewporter) {
if (s->wp_viewport) wp_viewport_destroy(s->wp_viewport);
s->wp_viewport = wp_viewporter_get_viewport(_glfw.wl.wp_viewporter, s->surface);
}
} }
#define damage_csd(which, xbuffer) if (decs.which.surface) { \ #define damage_csd(which, xbuffer) \
wl_surface_attach(decs.which.surface, (xbuffer), 0, 0); \ wl_surface_attach(decs.which.surface, (xbuffer), 0, 0); \
if (decs.which.wp_viewport) wp_viewport_set_destination(decs.which.wp_viewport, decs.which.buffer.viewport_width, decs.which.buffer.viewport_height); \
wl_surface_damage(decs.which.surface, 0, 0, decs.which.buffer.width, decs.which.buffer.height); \ wl_surface_damage(decs.which.surface, 0, 0, decs.which.buffer.width, decs.which.buffer.height); \
wl_surface_commit(decs.which.surface); \ wl_surface_commit(decs.which.surface); \
if (decs.which.buffer.a == (xbuffer)) { decs.which.buffer.a_needs_to_be_destroyed = false; } else { decs.which.buffer.b_needs_to_be_destroyed = false; }} if (decs.which.buffer.a == (xbuffer)) { decs.which.buffer.a_needs_to_be_destroyed = false; } else { decs.which.buffer.b_needs_to_be_destroyed = false; }
static bool bool
window_is_csd_capable(_GLFWwindow *window) {
return window->decorated && !decs.serverSide && window->wl.xdg.toplevel;
}
static bool
ensure_csd_resources(_GLFWwindow *window) { ensure_csd_resources(_GLFWwindow *window) {
if (!window_is_csd_capable(window)) return false; if (!window->decorated || window->wl.decorations.serverSide) return false;
const bool is_focused = window->id == _glfw.focusedWindowId; const bool is_focused = window->id == _glfw.focusedWindowId;
const bool focus_changed = is_focused != decs.for_window_state.focused; const bool focus_changed = is_focused != decs.for_window_state.focused;
const double current_scale = _glfwWaylandWindowScale(window);
const bool size_changed = ( const bool size_changed = (
decs.for_window_state.width != window->wl.width || decs.for_window_state.width != window->wl.width ||
decs.for_window_state.height != window->wl.height || decs.for_window_state.height != window->wl.height ||
decs.for_window_state.fscale != current_scale || decs.for_window_state.scale != _glfwWaylandIntegerWindowScale(window) ||
!decs.mapping.data !decs.mapping.data
); );
const bool state_changed = decs.for_window_state.toplevel_states != window->wl.current.toplevel_states; const bool needs_update = focus_changed || size_changed || !decs.left.surface || decs.buffer_destroyed;
const bool needs_update = focus_changed || size_changed || !decs.titlebar.surface || decs.buffer_destroyed || state_changed; debug("CSD: old.size: %dx%d new.size: %dx%d needs_update: %d size_changed: %d buffer_destroyed: %d\n",
debug("CSD: old.size: %dx%d new.size: %dx%d needs_update: %d size_changed: %d state_changed: %d buffer_destroyed: %d\n", decs.for_window_state.width, decs.for_window_state.height, window->wl.width, window->wl.height, needs_update, size_changed, decs.buffer_destroyed);
decs.for_window_state.width, decs.for_window_state.height, window->wl.width, window->wl.height, needs_update,
size_changed, state_changed, decs.buffer_destroyed);
if (!needs_update) return false; if (!needs_update) return false;
decs.for_window_state.fscale = current_scale; // used in create_shm_buffers
if (size_changed || decs.buffer_destroyed) { if (size_changed || decs.buffer_destroyed) {
free_csd_buffers(window); free_csd_buffers(window);
if (!create_shm_buffers(window)) return false; if (!create_shm_buffers(window)) return false;
decs.buffer_destroyed = false; decs.buffer_destroyed = false;
} }
#define setup_surface(which, x, y) \ int32_t x, y, scale = _glfwWaylandIntegerWindowScale(window);
if (!decs.which.surface) create_csd_surfaces(window, &decs.which); \ x = 0; y = -decs.metrics.top;
position_csd_surface(&decs.which, x, y); if (!decs.top.surface) create_csd_surfaces(window, &decs.top);
position_csd_surface(&decs.top, x, y, scale);
setup_surface(titlebar, 0, -decs.metrics.visible_titlebar_height); x = -decs.metrics.width; y = -decs.metrics.top;
setup_surface(shadow_top, decs.titlebar.x, decs.titlebar.y - decs.metrics.width); if (!decs.left.surface) create_csd_surfaces(window, &decs.left);
setup_surface(shadow_bottom, decs.titlebar.x, window->wl.height); position_csd_surface(&decs.left, x, y, scale);
setup_surface(shadow_left, -decs.metrics.width, decs.titlebar.y);
setup_surface(shadow_right, window->wl.width, decs.shadow_left.y);
setup_surface(shadow_upper_left, decs.shadow_left.x, decs.shadow_top.y);
setup_surface(shadow_upper_right, decs.shadow_right.x, decs.shadow_top.y);
setup_surface(shadow_lower_left, decs.shadow_left.x, decs.shadow_bottom.y);
setup_surface(shadow_lower_right, decs.shadow_right.x, decs.shadow_bottom.y);
if (focus_changed || state_changed) update_title_bar(window); x = -decs.metrics.width; y = window->wl.height;
damage_csd(titlebar, decs.titlebar.buffer.front); if (!decs.bottom.surface) create_csd_surfaces(window, &decs.bottom);
#define d(which) damage_csd(which, is_focused ? decs.which.buffer.front : decs.which.buffer.back); position_csd_surface(&decs.bottom, x, y, scale);
d(shadow_left); d(shadow_right); d(shadow_top); d(shadow_bottom);
d(shadow_upper_left); d(shadow_upper_right); d(shadow_lower_left); d(shadow_lower_right); x = window->wl.width; y = -decs.metrics.top;
#undef d if (!decs.right.surface) create_csd_surfaces(window, &decs.right);
position_csd_surface(&decs.right, x, y, scale);
if (focus_changed) update_title_bar(window);
damage_csd(top, decs.top.buffer.front);
damage_csd(left, is_focused ? decs.left.buffer.front : decs.left.buffer.back);
damage_csd(bottom, is_focused ? decs.bottom.buffer.front : decs.bottom.buffer.back);
damage_csd(right, is_focused ? decs.right.buffer.front : decs.right.buffer.back);
decs.for_window_state.width = window->wl.width; decs.for_window_state.width = window->wl.width;
decs.for_window_state.height = window->wl.height; decs.for_window_state.height = window->wl.height;
decs.for_window_state.scale = _glfwWaylandIntegerWindowScale(window);
decs.for_window_state.focused = is_focused; decs.for_window_state.focused = is_focused;
decs.for_window_state.toplevel_states = window->wl.current.toplevel_states;
return true; return true;
} }
void void
csd_set_visible(_GLFWwindow *window, bool visible) { free_all_csd_resources(_GLFWwindow *window) {
// When setting to visible will only take effect if window currently has
// CSD and will also ensure CSD is of correct size and type for current window.
// When hiding CSD simply destroys all CSD surfaces.
if (visible) ensure_csd_resources(window); else free_csd_surfaces(window);
}
void
csd_free_all_resources(_GLFWwindow *window) {
free_csd_surfaces(window); free_csd_surfaces(window);
free_csd_buffers(window); free_csd_buffers(window);
if (decs.shadow_tile.data) free(decs.shadow_tile.data); if (decs.shadow_tile.data) free(decs.shadow_tile.data);
decs.shadow_tile.data = NULL; decs.shadow_tile.data = NULL;
} }
bool void
csd_change_title(_GLFWwindow *window) { change_csd_title(_GLFWwindow *window) {
if (!window_is_csd_capable(window)) return false; if (!window->decorated || window->wl.decorations.serverSide) return;
if (ensure_csd_resources(window)) return true; // CSD were re-rendered for other reasons if (ensure_csd_resources(window)) return; // CSD were re-rendered for other reasons
if (decs.titlebar.surface) { if (decs.top.surface) {
update_title_bar(window); update_title_bar(window);
damage_csd(titlebar, decs.titlebar.buffer.front); damage_csd(top, decs.top.buffer.front);
return true;
} }
return false;
} }
void void
csd_set_window_geometry(_GLFWwindow *window, int32_t *width, int32_t *height) { set_csd_window_geometry(_GLFWwindow *window, int32_t *width, int32_t *height) {
bool has_csd = window_is_csd_capable(window) && decs.titlebar.surface && !(window->wl.current.toplevel_states & TOPLEVEL_STATE_FULLSCREEN); bool has_csd = window->decorated && !window->wl.decorations.serverSide && window->wl.decorations.left.surface && !(window->wl.current.toplevel_states & TOPLEVEL_STATE_FULLSCREEN);
bool size_specified_by_compositor = *width > 0 && *height > 0; bool size_specified_by_compositor = *width > 0 && *height > 0;
if (!size_specified_by_compositor) { if (!size_specified_by_compositor) {
*width = window->wl.user_requested_content_size.width; *width = window->wl.user_requested_content_size.width;
*height = window->wl.user_requested_content_size.height; *height = window->wl.user_requested_content_size.height;
if (window->wl.xdg.top_level_bounds.width > 0) *width = MIN(*width, window->wl.xdg.top_level_bounds.width);
if (window->wl.xdg.top_level_bounds.height > 0) *height = MIN(*height, window->wl.xdg.top_level_bounds.height);
if (has_csd) *height += decs.metrics.visible_titlebar_height; if (has_csd) *height += decs.metrics.visible_titlebar_height;
} }
decs.geometry.x = 0; decs.geometry.y = 0; decs.geometry.x = 0; decs.geometry.y = 0;
@@ -670,198 +449,12 @@ csd_set_window_geometry(_GLFWwindow *window, int32_t *width, int32_t *height) {
} }
} }
bool
csd_set_titlebar_color(_GLFWwindow *window, uint32_t color, bool use_system_color) {
bool use_custom_color = !use_system_color;
decs.use_custom_titlebar_color = use_custom_color;
decs.titlebar_color = color;
return csd_change_title(window);
}
#define x window->wl.allCursorPosX
#define y window->wl.allCursorPosY
static void
set_cursor(GLFWCursorShape shape, _GLFWwindow* window)
{
if (_glfw.wl.wp_cursor_shape_device_v1) {
wayland_cursor_shape s = glfw_cursor_shape_to_wayland_cursor_shape(shape);
if (s.which > -1) {
debug("Changing cursor shape to: %s with serial: %u\n", s.name, _glfw.wl.pointer_enter_serial);
wp_cursor_shape_device_v1_set_shape(_glfw.wl.wp_cursor_shape_device_v1, _glfw.wl.pointer_enter_serial, (uint32_t)s.which);
return;
}
}
struct wl_buffer* buffer;
struct wl_cursor* cursor;
struct wl_cursor_image* image;
struct wl_surface* surface = _glfw.wl.cursorSurface;
const int scale = _glfwWaylandIntegerWindowScale(window);
struct wl_cursor_theme *theme = glfw_wlc_theme_for_scale(scale);
if (!theme) return;
cursor = _glfwLoadCursor(shape, theme);
if (!cursor || !cursor->images) return;
image = cursor->images[0];
if (!image) return;
if (image->width % scale || image->height % scale) {
static uint32_t warned_width = 0, warned_height = 0;
if (warned_width != image->width || warned_height != image->height) {
_glfwInputError(GLFW_PLATFORM_ERROR, "WARNING: Cursor image size: %dx%d is not a multiple of window scale: %d. This will"
" cause some compositors such as GNOME to crash. See https://github.com/kovidgoyal/kitty/issues/4878", image->width, image->height, scale);
warned_width = image->width; warned_height = image->height;
}
}
buffer = wl_cursor_image_get_buffer(image);
if (!buffer) return;
debug("Calling wl_pointer_set_cursor in set_cursor with surface: %p\n", (void*)surface);
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.serial,
surface,
image->hotspot_x / scale,
image->hotspot_y / scale);
wl_surface_set_buffer_scale(surface, scale);
wl_surface_attach(surface, buffer, 0, 0);
wl_surface_damage(surface, 0, 0,
image->width, image->height);
wl_surface_commit(surface);
_glfw.wl.cursorPreviousShape = shape;
}
static bool
update_hovered_button(_GLFWwindow *window) {
bool has_hovered_button = false;
int scaled_x = (int)round(decs.for_window_state.fscale * x);
#define c(which) \
if (decs.which.left <= scaled_x && scaled_x < decs.which.left + decs.which.width) { \
has_hovered_button = true; \
if (!decs.which.hovered) { decs.titlebar_needs_update = true; decs.which.hovered = true; } \
} else if (decs.which.hovered) { decs.titlebar_needs_update = true; decs.which.hovered = false; }
c(minimize); c(maximize); c(close);
#undef c
update_title_bar(window);
return has_hovered_button;
}
static bool
has_hovered_button(_GLFWwindow *window) {
return decs.minimize.hovered || decs.maximize.hovered || decs.close.hovered;
}
static void
handle_pointer_leave(_GLFWwindow *window, struct wl_surface *surface) {
#define c(which) if (decs.which.hovered) { decs.titlebar_needs_update = true; decs.which.hovered = false; }
if (surface == decs.titlebar.surface) {
c(minimize); c(maximize); c(close);
}
#undef c
decs.focus = CENTRAL_WINDOW;
decs.dragging = false;
}
static void
handle_pointer_move(_GLFWwindow *window) {
GLFWCursorShape cursorShape = GLFW_DEFAULT_CURSOR;
switch (decs.focus)
{
case CENTRAL_WINDOW: break;
case CSD_titlebar: {
if (decs.dragging) {
if (window->wl.xdg.toplevel) xdg_toplevel_move(window->wl.xdg.toplevel, _glfw.wl.seat, _glfw.wl.pointer_serial);
} else if (update_hovered_button(window)) cursorShape = GLFW_POINTER_CURSOR;
} break;
case CSD_shadow_top: cursorShape = GLFW_N_RESIZE_CURSOR; break;
case CSD_shadow_bottom: cursorShape = GLFW_S_RESIZE_CURSOR; break;
case CSD_shadow_left: cursorShape = GLFW_W_RESIZE_CURSOR; break;
case CSD_shadow_right: cursorShape = GLFW_E_RESIZE_CURSOR; break;
case CSD_shadow_upper_left: cursorShape = GLFW_NW_RESIZE_CURSOR; break;
case CSD_shadow_upper_right: cursorShape = GLFW_NE_RESIZE_CURSOR; break;
case CSD_shadow_lower_left: cursorShape = GLFW_SW_RESIZE_CURSOR; break;
case CSD_shadow_lower_right: cursorShape = GLFW_SE_RESIZE_CURSOR; break;
}
if (_glfw.wl.cursorPreviousShape != cursorShape) set_cursor(cursorShape, window);
}
static void
handle_pointer_enter(_GLFWwindow *window, struct wl_surface *surface) {
#define Q(which) if (decs.which.surface == surface) { \
decs.focus = CSD_##which; handle_pointer_move(window); return; } // enter is also a move
all_surfaces(Q)
#undef Q
decs.focus = CENTRAL_WINDOW;
decs.dragging = false;
}
static void
handle_pointer_button(_GLFWwindow *window, uint32_t button, uint32_t state) {
uint32_t edges = XDG_TOPLEVEL_RESIZE_EDGE_NONE;
if (button == BTN_LEFT) {
switch (decs.focus) {
case CENTRAL_WINDOW: break;
case CSD_titlebar:
if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
monotonic_t last_click_at = decs.last_click_on_top_decoration_at;
decs.last_click_on_top_decoration_at = monotonic();
if (decs.last_click_on_top_decoration_at - last_click_at <= _glfwPlatformGetDoubleClickInterval(window)) {
decs.last_click_on_top_decoration_at = 0;
if (window->wl.current.toplevel_states & TOPLEVEL_STATE_MAXIMIZED) _glfwPlatformRestoreWindow(window);
else _glfwPlatformMaximizeWindow(window);
return;
}
} else {
if (decs.minimize.hovered) _glfwPlatformIconifyWindow(window);
else if (decs.maximize.hovered) {
if (window->wl.current.toplevel_states & TOPLEVEL_STATE_MAXIMIZED) _glfwPlatformRestoreWindow(window);
else _glfwPlatformMaximizeWindow(window);
// hack otherwise on GNOME maximize button remains hovered sometimes
decs.maximize.hovered = false; decs.titlebar_needs_update = true;
} else if (decs.close.hovered) _glfwInputWindowCloseRequest(window);
}
decs.dragging = !has_hovered_button(window);
break;
case CSD_shadow_left: edges = XDG_TOPLEVEL_RESIZE_EDGE_LEFT; break;
case CSD_shadow_upper_left: edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT; break;
case CSD_shadow_right: edges = XDG_TOPLEVEL_RESIZE_EDGE_RIGHT; break;
case CSD_shadow_upper_right: edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT; break;
case CSD_shadow_top: edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP; break;
case CSD_shadow_lower_left: edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT; break;
case CSD_shadow_bottom: edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM; break;
case CSD_shadow_lower_right: edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT; break;
}
if (edges != XDG_TOPLEVEL_RESIZE_EDGE_NONE) xdg_toplevel_resize(window->wl.xdg.toplevel, _glfw.wl.seat, _glfw.wl.pointer_serial, edges);
}
else if (button == BTN_RIGHT) {
if (decs.focus == CSD_titlebar && window->wl.xdg.toplevel)
{
if (window->wl.wm_capabilities.window_menu) xdg_toplevel_show_window_menu(
window->wl.xdg.toplevel, _glfw.wl.seat, _glfw.wl.pointer_serial, (int32_t)x, (int32_t)y - decs.metrics.top);
else
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland compositor does not support showing wndow menu");
return;
}
}
}
void void
csd_handle_pointer_event(_GLFWwindow *window, int button, int state, struct wl_surface *surface) { set_titlebar_color(_GLFWwindow *window, uint32_t color, bool use_system_color) {
if (!window_is_csd_capable(window)) return; bool use_custom_color = !use_system_color;
decs.titlebar_needs_update = false; if (use_custom_color != decs.use_custom_titlebar_color || color != decs.titlebar_color) {
switch (button) { decs.use_custom_titlebar_color = use_custom_color;
case -1: handle_pointer_move(window); break; decs.titlebar_color = color;
case -2: handle_pointer_enter(window, surface); break;
case -3: handle_pointer_leave(window, surface); break;
default: handle_pointer_button(window, button, state); break;
}
if (decs.titlebar_needs_update) {
csd_change_title(window);
if (!window->wl.waiting_for_swap_to_commit) wl_surface_commit(window->wl.surface);
} }
change_csd_title(window);
} }
#undef x
#undef y

View File

@@ -8,10 +8,10 @@
#include "internal.h" #include "internal.h"
void csd_initialize_metrics(_GLFWwindow *window); void initialize_csd_metrics(_GLFWwindow *window);
void csd_free_all_resources(_GLFWwindow *window); void free_all_csd_resources(_GLFWwindow *window);
bool csd_change_title(_GLFWwindow *window); void free_csd_surfaces(_GLFWwindow *window);
void csd_set_window_geometry(_GLFWwindow *window, int32_t *width, int32_t *height); void change_csd_title(_GLFWwindow *window);
bool csd_set_titlebar_color(_GLFWwindow *window, uint32_t color, bool use_system_color); bool ensure_csd_resources(_GLFWwindow *window);
void csd_set_visible(_GLFWwindow *window, bool visible); void set_csd_window_geometry(_GLFWwindow *window, int32_t *width, int32_t *height);
void csd_handle_pointer_event(_GLFWwindow *window, int button, int state, struct wl_surface* surface); void set_titlebar_color(_GLFWwindow *window, uint32_t color, bool use_system_color);

246
glfw/wl_decor.c vendored Normal file
View File

@@ -0,0 +1,246 @@
/*
* wl_decor.c
* Copyright (C) 2024 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#define _POSIX_C_SOURCE 200809L
#include "wl_decor.h"
#include "wl_client_side_decorations.h"
#include "libdecor-0/libdecor.h"
#include <dlfcn.h>
#include <string.h>
#include <stdlib.h>
// Boilerplate to dynload libdecor {{{
#define funcs F(libdecor_new); F(libdecor_unref); F(libdecor_get_fd); F(libdecor_dispatch); F(libdecor_decorate); F(libdecor_frame_unref); \
F(libdecor_frame_set_app_id); F(libdecor_frame_set_title); F(libdecor_frame_set_minimized); F(libdecor_frame_set_fullscreen); \
F(libdecor_frame_unset_fullscreen); F(libdecor_frame_map); F(libdecor_frame_commit); F(libdecor_frame_set_min_content_size); \
F(libdecor_frame_set_max_content_size); F(libdecor_frame_set_maximized); F(libdecor_frame_unset_maximized); \
F(libdecor_frame_set_capabilities); F(libdecor_frame_unset_capabilities); F(libdecor_frame_set_visibility); \
F(libdecor_frame_get_xdg_toplevel); F(libdecor_configuration_get_content_size); F(libdecor_configuration_get_window_state); \
F(libdecor_state_new); F(libdecor_state_free);
#define F(name) __typeof__(name) (*name)
static struct {
void* libdecor_handle;
funcs
} libdecor_funcs = {0};
#undef F
#define LOAD_FUNC(handle, name) { \
glfw_dlsym(libdecor_funcs.name, handle, #name); \
if (!libdecor_funcs.name) { \
const char* error = dlerror(); \
_glfwInputError(GLFW_PLATFORM_ERROR, "failed to load libdecor function %s with error: %s", #name, error ? error : "(null)"); \
dlclose(handle); handle = NULL; return false; \
memset(&libdecor_funcs, 0, sizeof(libdecor_funcs)); \
} \
}
static bool
glfw_wl_load_libdecor(void) {
if (libdecor_funcs.libdecor_handle != NULL) return true;
const char* libnames[] = {
#ifdef _GLFW_DECOR_LIBRARY
_GLFW_DECOR_LIBRARY,
#else
"libdecor-0.so",
// some installs are missing the .so symlink, so try the full name
"libdecor-0.so.0",
#endif
NULL
};
for (int i = 0; libnames[i]; i++) {
libdecor_funcs.libdecor_handle = _glfw_dlopen(libnames[i]);
if (libdecor_funcs.libdecor_handle) break;
}
if (!libdecor_funcs.libdecor_handle) {
libdecor_funcs.libdecor_handle = _glfw_dlopen(libnames[0]);
if (!libdecor_funcs.libdecor_handle) {
_glfwInputError(GLFW_PLATFORM_ERROR, "failed to dlopen %s with error: %s", libnames[0], dlerror());
return false;
}
}
dlerror(); /* Clear any existing error */
#define F(name) LOAD_FUNC(libdecor_funcs.libdecor_handle, name)
funcs
#undef F
return true;
}
#define libdecor_new libdecor_funcs.libdecor_new
#define libdecor_unref libdecor_funcs.libdecor_unref
#define libdecor_get_fd libdecor_funcs.libdecor_get_fd
#define libdecor_dispatch libdecor_funcs.libdecor_dispatch
#define libdecor_decorate libdecor_funcs.libdecor_decorate
#define libdecor_frame_unref libdecor_funcs.libdecor_frame_unref
#define libdecor_frame_set_app_id libdecor_funcs.libdecor_frame_set_app_id
#define libdecor_frame_set_title libdecor_funcs.libdecor_frame_set_title
#define libdecor_frame_set_minimized libdecor_funcs.libdecor_frame_set_minimized
#define libdecor_frame_set_fullscreen libdecor_funcs.libdecor_frame_set_fullscreen
#define libdecor_frame_unset_fullscreen libdecor_funcs.libdecor_frame_unset_fullscreen
#define libdecor_frame_map libdecor_funcs.libdecor_frame_map
#define libdecor_frame_commit libdecor_funcs.libdecor_frame_commit
#define libdecor_frame_set_min_content_size libdecor_funcs.libdecor_frame_set_min_content_size
#define libdecor_frame_set_max_content_size libdecor_funcs.libdecor_frame_set_max_content_size
#define libdecor_frame_set_maximized libdecor_funcs.libdecor_frame_set_maximized
#define libdecor_frame_unset_maximized libdecor_funcs.libdecor_frame_unset_maximized
#define libdecor_frame_set_capabilities libdecor_funcs.libdecor_frame_set_capabilities
#define lilibdecor_frame_set_capabilities libdecor_funcs.lilibdecor_frame_set_capabilities
#define libdecor_frame_set_visibility libdecor_funcs.libdecor_frame_set_visibility
#define libdecor_frame_get_xdg_toplevel libdecor_funcs.libdecor_frame_get_xdg_toplevel
#define libdecor_configuration_get_content_size libdecor_funcs.libdecor_configuration_get_content_size
#define libdecor_configuration_get_window_state libdecor_funcs.libdecor_configuration_get_window_state
#define libdecor_state_new libdecor_funcs.libdecor_state_new
#define libdecor_state_free libdecor_funcs.libdecor_state_free
// }}}
typedef struct DecorLibState {
struct libdecor* libdecor;
} DecorLibState;
void handle_libdecor_error(struct libdecor* context UNUSED, enum libdecor_error error, const char* message) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: libdecor error %u: %s", error, message);
}
static struct libdecor_interface libdecor_interface = {
.error = handle_libdecor_error
};
static DECOR_LIB_HANDLE
glfw_wl_load_decorations_library_(struct wl_display *display) {
if (!glfw_wl_load_libdecor()) return NULL;
DecorLibState *ans = calloc(1, sizeof(DecorLibState));
if (!ans) { _glfwInputError(GLFW_PLATFORM_ERROR, "Out of memory"); return NULL; }
ans->libdecor = libdecor_new(display, &libdecor_interface);
if (!ans->libdecor) _glfwInputError(GLFW_PLATFORM_ERROR, "libdecor_new() returned NULL");
return (DECOR_LIB_HANDLE) ans;
}
DECOR_LIB_HANDLE
glfw_wl_load_decorations_library(struct wl_display *display) {
// See https://gitlab.freedesktop.org/libdecor/libdecor/-/issues/65 for why we need this nautanki with GDK_BACKEND
char *gdk_backend = getenv("GDK_BACKEND");
if (gdk_backend && strcmp(gdk_backend, "wayland") != 0) {
gdk_backend = strdup(gdk_backend);
setenv("GDK_BACKEND", "wayland", 1);
}
DECOR_LIB_HANDLE ans = glfw_wl_load_decorations_library_(display);
if (gdk_backend) {
setenv("GDK_BACKEND", gdk_backend, 1);
free(gdk_backend);
}
return ans;
}
void
glfw_wl_unload_decorations_library(DECOR_LIB_HANDLE h_) {
if (h_) {
DecorLibState *h = (DecorLibState*)h_;
if (h->libdecor) { libdecor_unref(h->libdecor); }
free(h);
}
if (libdecor_funcs.libdecor_handle) {
dlclose(libdecor_funcs.libdecor_handle); libdecor_funcs.libdecor_handle = NULL;
memset(&libdecor_funcs, 0, sizeof(libdecor_funcs));
}
}
int
glfw_wl_dispatch_decor_events(void) {
// TODO: change this to just call while (g_main_context_iteration(NULL, FALSE)); when using the gtk plugin
// will require a patch to libdecor. The libdecor API currently has no way to either tell what plugin
// is being used or to just dispatch non-Wayland events.
// https://gitlab.freedesktop.org/libdecor/libdecor/-/issues/70
return libdecor_dispatch(((DecorLibState*)_glfw.wl.decor)->libdecor, 0);
}
typedef struct Frame {
struct libdecor_frame *libdecor;
} Frame;
void
glfw_wl_set_fullscreen(_GLFWwindow *w, bool on, struct wl_output *monitor) {
Frame *d = (Frame*)w->wl.frame;
if (d && d->libdecor) {
if (on) libdecor_frame_set_fullscreen(d->libdecor, monitor);
else libdecor_frame_unset_fullscreen(d->libdecor);
} else if (w->wl.xdg.toplevel) {
if (on) {
xdg_toplevel_set_fullscreen(w->wl.xdg.toplevel, monitor);
if (!w->wl.decorations.serverSide) free_csd_surfaces(w);
} else {
xdg_toplevel_unset_fullscreen(w->wl.xdg.toplevel);
ensure_csd_resources(w);
}
}
}
void
glfw_wl_set_maximized(_GLFWwindow *w, bool on) {
Frame *d = (Frame*)w->wl.frame;
if (d && d->libdecor) {
if (on) libdecor_frame_set_maximized(d->libdecor);
else libdecor_frame_unset_maximized(d->libdecor);
} else if (w->wl.xdg.toplevel) {
if (on) xdg_toplevel_set_maximized(w->wl.xdg.toplevel);
else xdg_toplevel_unset_maximized(w->wl.xdg.toplevel);
}
}
void
glfw_wl_set_minimized(_GLFWwindow *w) {
Frame *d = (Frame*)w->wl.frame;
if (d && d->libdecor) libdecor_frame_set_minimized(d->libdecor);
else if (w->wl.xdg.toplevel) xdg_toplevel_set_minimized(w->wl.xdg.toplevel);
}
void
glfw_wl_set_title(_GLFWwindow *w, const char *title) {
// Wayland cannot handle requests larger than ~8200 bytes. Sending
// one causes an abort(). Since titles this large are meaningless anyway
// ensure they do not happen.
if (!title) title = "";
char *safe_title = utf_8_strndup(title, 2048);
if (!safe_title) return;
if (w->wl.title && strcmp(w->wl.title, safe_title) == 0) { free(safe_title); return; }
free(w->wl.title); w->wl.title = safe_title;
Frame *d = (Frame*)w->wl.frame;
if (d && d->libdecor) libdecor_frame_set_title(d->libdecor, w->wl.title);
else if (w->wl.xdg.toplevel) {
xdg_toplevel_set_title(w->wl.xdg.toplevel, w->wl.title);
change_csd_title(w);
}
}
void
glfw_wl_set_app_id(_GLFWwindow *w, const char *appid) {
if (!appid || !appid[0]) return;
Frame *d = (Frame*)w->wl.frame;
if (d && d->libdecor) libdecor_frame_set_app_id(d->libdecor, appid);
else if (w->wl.xdg.toplevel) xdg_toplevel_set_app_id(w->wl.xdg.toplevel, appid);
}
void
glfw_wl_set_size_limits(_GLFWwindow *w, int minwidth, int minheight, int maxwidth, int maxheight) {
if (w->wl.xdg.toplevel) {
minwidth = minwidth == GLFW_DONT_CARE ? 0 : minwidth;
minheight = minheight == GLFW_DONT_CARE ? 0 : minheight;
maxwidth = maxwidth == GLFW_DONT_CARE ? 0 : maxwidth;
maxheight = maxheight == GLFW_DONT_CARE ? 0 : maxheight;
Frame *d = (Frame*)w->wl.frame;
if (d && d->libdecor) {
libdecor_frame_set_min_content_size(d->libdecor, minwidth, minheight);
libdecor_frame_set_max_content_size(d->libdecor, maxwidth, maxheight);
} else {
xdg_toplevel_set_min_size(w->wl.xdg.toplevel, minwidth, minheight);
xdg_toplevel_set_max_size(w->wl.xdg.toplevel, maxwidth, maxheight);
}
}
}

22
glfw/wl_decor.h vendored Normal file
View File

@@ -0,0 +1,22 @@
/*
* wl_decor.h
* Copyright (C) 2024 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#pragma once
#include <wayland-client.h>
#include <stdbool.h>
#include "internal.h"
DECOR_LIB_HANDLE glfw_wl_load_decorations_library(struct wl_display*);
void glfw_wl_unload_decorations_library(DECOR_LIB_HANDLE);
int glfw_wl_dispatch_decor_events(void);
void glfw_wl_set_fullscreen(_GLFWwindow *w, bool on, struct wl_output *monitor);
void glfw_wl_set_maximized(_GLFWwindow *w, bool on);
void glfw_wl_set_minimized(_GLFWwindow *w);
void glfw_wl_set_title(_GLFWwindow *w, const char *title);
void glfw_wl_set_app_id(_GLFWwindow *w, const char *appid);
void glfw_wl_set_size_limits(_GLFWwindow *w, int minwidth, int minheight, int maxwidth, int maxheight);

Some files were not shown because too many files have changed in this diff Show More