Compare commits

..

4 Commits

Author SHA1 Message Date
Kovid Goyal
c10e421e9b Implement dynamic control of transparent background colors via escape code
Still have to implement it via remote control
2024-09-20 12:56:16 +05:30
Kovid Goyal
958cc6d98f Store transparent colors on ColorProfile
This will eventually allow them to be changed using remote control and
escape codes.
2024-09-20 10:02:28 +05:30
Kovid Goyal
f1f4a1a4a3 Replace the second_transparent_bg option
This is backwards incompatible, but only for a feature released 3 weeks
ago.
2024-09-20 09:11:39 +05:30
Kovid Goyal
55859f4b56 Prepare for allowing upto seven additional semi-transparent background colors 2024-09-20 09:11:39 +05:30
646 changed files with 40204 additions and 67636 deletions

View File

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

19
.gitattributes vendored
View File

@@ -1,18 +1,14 @@
kitty/terminfo.h linguist-generated=true
terminfo/kitty.termcap linguist-generated=true
terminfo/kitty.terminfo linguist-generated=true
terminfo/x/xterm-kitty linguist-generated=true
kitty/char-props-data.h linguist-generated=true
kitty_tests/GraphemeBreakTest.json linguist-generated=true
kitty/wcwidth-std.h linguist-generated=true
kitty/emoji.h linguist-generated=true
kitty/charsets.c 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/rgb.py linguist-generated=true
kitty/srgb_gamma.* linguist-generated=true
kitty/gl-wrapper.* linguist-generated=true
kitty/glfw-wrapper.* 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/parse.py linguist-generated=true
kitty/options/to-c-generated.h linguist-generated=true
@@ -22,12 +18,15 @@ glfw/*.c linguist-vendored=true
glfw/*.h linguist-vendored=true
3rdparty/** linguist-vendored=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
terminfo/kitty.term* linguist-generated=true
terminfo/x/* linguist-generated=true
*_generated.* linguist-generated=true
*_generated_test.* linguist-generated=true
*_generated.h linguist-generated=true
*_generated.go linguist-generated=true
*_generated_test.go linguist-generated=true
*_generated_test.s linguist-generated=true
*_generated.s linguist-generated=true
*.py text diff=python
*.m text diff=objc

View File

@@ -23,7 +23,7 @@ If applicable, add screenshots to help explain your problem.
**Environment details**
```
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
```

View File

@@ -13,15 +13,3 @@ updates:
all-go-deps:
patterns:
- "*" # group all non-security update PRs
cooldown:
default-days: 7
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
groups:
actions:
patterns:
- "*"
cooldown:
default-days: 7

View File

@@ -4,7 +4,6 @@
import glob
import io
import json
import os
import shlex
import shutil
@@ -12,13 +11,12 @@ import subprocess
import sys
import tarfile
import time
from urllib.request import Request
from urllib.request import urlopen
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_codeql = os.environ.get('KITTY_CODEQL') == '1'
is_macos = 'darwin' in sys.platform.lower()
SW = ''
@@ -63,57 +61,36 @@ def run(*a: str, print_crash_reports: bool = False) -> None:
raise SystemExit(f'The following process failed with exit code: {ret}:\n{cmd}')
def download_with_retry(url: str | Request, count: int = 5) -> bytes:
from urllib.request import urlopen
for i in range(count):
try:
print('Downloading', url, flush=True)
with urlopen(url) as f:
ans: bytes = f.read()
return ans
except Exception as err:
if i >= count - 1:
raise
print(f'Download failed with error {err} retrying...', file=sys.stderr)
time.sleep(1)
return b''
def install_fonts() -> None:
data = download_with_retry(FONTS_URL)
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)
data = download_with_retry(NERD_URL)
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)
tf.extractall(fonts_dir)
def install_deps() -> None:
print('Installing kitty dependencies...')
sys.stdout.flush()
if is_macos:
if not is_codeql: # for some reason brew fails on CodeQL we dont need it anyway
items = [x.split()[1].strip('"') for x in open('Brewfile').readlines() if x.strip().startswith('brew ')]
openssl = 'openssl'
items.remove('go') # already installed by ci.yml
import ssl
if ssl.OPENSSL_VERSION_INFO[0] == 1:
openssl += '@1.1'
run('brew', 'install', 'fish', openssl, *items)
items = [x.split()[1].strip('"') for x in open('Brewfile').readlines() if x.strip().startswith('brew ')]
openssl = 'openssl'
items.remove('go') # already installed by ci.yml
import ssl
if ssl.OPENSSL_VERSION_INFO[0] == 1:
openssl += '@1.1'
run('brew', 'install', 'fish', openssl, *items)
else:
run('sudo apt-get update')
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'
' 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 libsystemd-dev zsh bash dash systemd-coredump gdb')
# for some reason these directories are world writable which causes zsh
# compinit to break
run('sudo chmod -R og-w /usr/share/zsh')
@@ -171,92 +148,26 @@ def setup_bundle_env() -> None:
os.environ['PATH'] = '{}:{}'.format(os.path.join(SW, 'bin'), os.environ['PATH'])
def install_bundle(dest: str = '', which: str = '') -> None:
dest = dest or SW
def install_bundle() -> None:
cwd = os.getcwd()
os.makedirs(dest, exist_ok=True)
os.chdir(dest)
which = which or ('macos' if is_macos else 'linux')
data = download_with_retry(BUNDLE_URL.format(which))
os.makedirs(SW)
os.chdir(SW)
with urlopen(BUNDLE_URL.format('macos' if is_macos else 'linux')) as f:
data = f.read()
with tarfile.open(fileobj=io.BytesIO(data), mode='r:xz') as tf:
try:
tf.extractall(filter='fully_trusted')
except TypeError:
tf.extractall()
tf.extractall()
if not is_macos:
replaced = 0
for dirpath, dirnames, filenames in os.walk('.'):
for f in filenames:
if f.endswith('.pc') or (f.endswith('.py') and f.startswith('_sysconfig')):
replace_in_file(os.path.join(dirpath, f), '/sw/sw', dest)
replace_in_file(os.path.join(dirpath, f), '/sw/sw', SW)
replaced += 1
if replaced < 2:
raise SystemExit('Failed to replace path to SW in bundle')
os.chdir(cwd)
def install_grype() -> str:
dest = '/tmp'
rq = Request('https://api.github.com/repos/anchore/grype/releases/latest', headers={
'Accept': 'application/vnd.github.v3+json',
})
m = json.loads(download_with_retry(rq))
for asset in m['assets']:
if asset['name'].endswith('_linux_amd64.tar.gz'):
url = asset['browser_download_url']
break
else:
raise ValueError('Could not find linux binary for grype')
os.makedirs(dest, exist_ok=True)
data = download_with_retry(url)
with tarfile.open(fileobj=io.BytesIO(data), mode='r') as tf:
tf.extract('grype', path=dest, filter='fully_trusted')
exe = os.path.join(dest, 'grype')
subprocess.check_call([exe, 'db', 'update'])
return exe
IGNORED_DEPENDENCY_CVES = [
# Python stdlib
'CVE-2025-8194', # DoS in tarfile
'CVE-2025-6069', # DoS in HTMLParser
'CVE-2025-13836', # DoS in http client reading from malicious server
'CVE-2025-12084', # DoS in xml.dom.minidom unused in kitty
# glib
'CVE-2025-4056', # Only affects Windows, on which we dont run
# github.com/nwaples/rardecode/v2
'CVE-2025-11579', # rardecode is version 2.2.1, not vulnerable
]
def check_dependencies() -> None:
grype = install_grype()
with open((gc := os.path.expanduser('~/.grype.yml')), 'w') as f:
print('ignore:', file=f)
for x in IGNORED_DEPENDENCY_CVES:
print(' - vulnerability:', x, file=f)
dest = os.path.join(SW, 'linux')
os.makedirs(dest, exist_ok=True)
install_bundle(dest, os.path.basename(dest))
dest = os.path.join(SW, 'macos')
os.makedirs(dest, exist_ok=True)
install_bundle(dest, os.path.basename(dest))
cmdline = [grype, '--by-cve', '--config', gc, '--fail-on', 'medium', '--only-fixed', '--add-cpes-if-none']
if (subprocess.run(cmdline + ['dir:' + SW])).returncode != 0:
raise SystemExit('grype found problems during filesystem scan')
# Now test against the SBOM
import runpy
orig = sys.argv, sys.stdout
sys.argv = ['bypy', 'sbom', 'myproject', '1.0.0']
buf = io.StringIO()
sys.stdout = buf
runpy.run_path('bypy-src')
sys.argv, sys.stdout = orig
print(buf.getvalue())
if (subprocess.run(cmdline, input=buf.getvalue().encode())).returncode != 0:
raise SystemExit('grype found problems during SBOM scan')
def main() -> None:
if is_bundle:
setup_bundle_env()
@@ -272,20 +183,12 @@ def main() -> None:
package_kitty()
elif action == 'test':
test_kitty()
elif action == 'test':
test_kitty()
elif action == 'govulncheck':
subprocess.check_call(['go', 'install', 'golang.org/x/vuln/cmd/govulncheck@latest'])
subprocess.check_call(['govulncheck', '-mode=binary', 'kitty/launcher/kitten'])
subprocess.check_call(['govulncheck', './...'])
elif action == 'gofmt':
q = subprocess.check_output('gofmt -s -l tools kittens'.split()).decode()
q = subprocess.check_output('gofmt -s -l tools'.split()).decode()
if q.strip():
q = '\n'.join(filter(lambda x: not x.rstrip().endswith('_generated.go'), q.strip().splitlines())).strip()
if q:
raise SystemExit(q)
elif action == 'check-dependencies':
check_dependencies()
else:
raise SystemExit(f'Unknown action: {action}')

View File

@@ -23,15 +23,15 @@ jobs:
cc: [gcc, clang]
include:
- python: a
pyver: "3.10"
pyver: "3.9"
sanitize: 0
- python: b
pyver: "3.11"
pyver: "3.10"
sanitize: 1
- python: c
pyver: "3.12"
pyver: "3.11"
sanitize: 1
@@ -45,18 +45,17 @@ jobs:
steps:
- name: Checkout source code
uses: actions/checkout@v6
uses: actions/checkout@v3
with:
fetch-depth: 10
persist-credentials: false
- name: Set up Python ${{ matrix.pyver }}
uses: actions/setup-python@v6
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.pyver }}
- name: Install Go
uses: actions/setup-go@v6
uses: actions/setup-go@v4
with:
go-version-file: go.mod
@@ -73,30 +72,26 @@ jobs:
CFLAGS: -funsigned-char
steps:
- name: Checkout source code
uses: actions/checkout@v6
uses: actions/checkout@v3
with:
fetch-depth: 0 # needed for :commit: docs role
persist-credentials: false
- 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
- 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
uses: actions/setup-python@v6
uses: actions/setup-python@v4
with:
python-version: "3.13"
python-version: "3.12"
- name: Install Go
uses: actions/setup-go@v6
uses: actions/setup-go@v4
with:
go-version-file: go.mod
cache: false
- name: Cache Go build artifacts separately
uses: actions/cache@v5
uses: actions/cache@v3
with:
path: |
~/.cache/go-build
@@ -124,7 +119,7 @@ jobs:
run: which python && python -m mypy --version && ./test.py mypy
- name: Run go vet
run: go version && go vet -tags testing ./...
run: go version && go vet ./...
- name: Build man page
run: make FAIL_WARN=1 man
@@ -145,13 +140,12 @@ jobs:
KITTY_BUNDLE: 1
steps:
- name: Checkout source code
uses: actions/checkout@v6
uses: actions/checkout@v3
with:
fetch-depth: 10
persist-credentials: false
- name: Install Go
uses: actions/setup-go@v6
uses: actions/setup-go@v4
with:
go-version-file: go.mod
@@ -166,18 +160,17 @@ jobs:
runs-on: macos-latest
steps:
- name: Checkout source code
uses: actions/checkout@v6
uses: actions/checkout@v3
with:
fetch-depth: 0 # needed for :commit: docs role
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@v4
with:
python-version: "3.11"
python-version: "3.10"
- name: Install Go
uses: actions/setup-go@v6
uses: actions/setup-go@v4
with:
go-version-file: go.mod
@@ -195,30 +188,3 @@ jobs:
- name: Build kitty 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@v6
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@v6
with:
go-version-file: go.mod
- name: Build kitty
run: ./dev.sh build
- name: Run benchmarks
run: ./benchmark.py

View File

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

View File

@@ -1,39 +0,0 @@
name: Depscan
on:
push:
branches: [master]
schedule:
- cron: '0 12 * * 5'
env:
CI: 'true'
ASAN_OPTIONS: detect_leaks=0
LC_ALL: en_US.UTF-8
LANG: en_US.UTF-8
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
dependecy-scanner:
name: Scan dependencies for vulnerabilities
runs-on: ubuntu-latest
env:
KITTY_BUNDLE: 1
steps:
- name: Checkout source code
uses: actions/checkout@v6
with:
fetch-depth: 10
persist-credentials: false
- name: Checkout bypy
uses: actions/checkout@v6
with:
fetch-depth: 1
persist-credentials: false
repository: kovidgoyal/bypy
path: bypy-src
- name: Check dependencies
run: python3 .github/workflows/ci.py check-dependencies

1
.ignore Normal file
View File

@@ -0,0 +1 @@
kittens/unicode_input/names.h

View File

@@ -1,3 +1,4 @@
brew "pkg-config"
brew "zlib"
brew "xxhash"
brew "simde"

View File

@@ -1,7 +1,5 @@
= kitty - the fast, feature-rich, cross-platform, GPU based terminal
If you live in the terminal, *kitty* is made for **you**!
See https://sw.kovidgoyal.net/kitty/[the kitty website].
image:https://github.com/kovidgoyal/kitty/workflows/CI/badge.svg["Build status", link="https://github.com/kovidgoyal/kitty/actions?query=workflow%3ACI"]
@@ -13,4 +11,4 @@ https://www.reddit.com/r/KittyTerminal[Reddit community]
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

@@ -7,12 +7,6 @@ and released just like all other bugs.
## Reporting a vulnerability
Preferably send an email to kovid at kovidgoyal.net or open a private security
advisory using the GitHub security advisory facility.
Note that I will generally respond to security communication within 72 hours. Once
the bug is confirmed, it will be fixed or at least mitigated within another 72
hours, at which time the fix will typically be committed to master and hence be
public. That timeline might be extended based on the severity of the issue and the
current state of master in terms of making a new release, if so, it will be
done in consultation with the issue reporter.
Preferably send an email to kovid at kovidgoyal.net or open an issue in the
GitHub repository, though the latter means you are disclosing the vulnerability
publicly before it can be fixed.

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|memoryview) -> 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

@@ -89,7 +89,7 @@ def build_frozen_tools(kitty_exe):
def sanitize_source_folder(path: str) -> None:
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)

View File

@@ -26,7 +26,7 @@ def binary_includes():
return tuple(map(get_dll_path, (
'expat', 'sqlite3', 'ffi', 'z', 'lzma', 'png16', 'lcms2', 'ssl', 'crypto', 'crypt',
'iconv', 'pcre2-8', '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
# distro fashion, different distros use different default config
# paths for fontconfig.

View File

@@ -95,8 +95,7 @@ def expand_dirs(items, exclude=lambda x: x.endswith('.so')):
return items
def do_sign(app_dir: str) -> None:
def do_sign(app_dir):
with current_dir(join(app_dir, 'Contents')):
# Sign all .so files
so_files = {x for x in files_in('.') if x.endswith('.so')}
@@ -110,10 +109,6 @@ def do_sign(app_dir: str) -> None:
# Sign kitten
with current_dir('MacOS'):
codesign('kitten')
# Sign sub-apps
for x in os.listdir('.'):
if x.endswith('.app'):
codesign(x)
# Now sign the main app
codesign(app_dir)
@@ -135,7 +130,7 @@ def sign_app(app_dir, notarize):
with make_certificate_useable():
do_sign(app_dir)
if notarize:
notarize_app(app_dir, 'kitty')
notarize_app(app_dir)
class Freeze(object):
@@ -173,7 +168,6 @@ class Freeze(object):
self.freeze_python()
self.add_ca_certs()
self.build_frozen_tools()
self.complete_sub_bundles()
if not self.dont_strip:
self.strip_files()
if not self.skip_tests:
@@ -184,16 +178,6 @@ class Freeze(object):
return ret
@flush
def complete_sub_bundles(self):
count = 0
for qapp in glob.glob(join(self.contents_dir, '*.app')):
for exe in glob.glob(join(self.contents_dir, 'MacOS', '*')):
os.symlink(f'../../../MacOS/{os.path.basename(exe)}', os.path.join(qapp, 'Contents', 'MacOS', os.path.basename(exe)))
count += 1
if count < 2:
raise SystemExit(f'Could not complete sub-bundles in {self.contents_dir}')
@flush
def add_ca_certs(self):
print('\nDownloading CA certs...')
@@ -387,9 +371,6 @@ class Freeze(object):
os.rename(join(dirname(self.contents_dir), 'bin', 'kitty'), join(self.contents_dir, 'MacOS', 'kitty'))
shutil.rmtree(join(dirname(self.contents_dir), 'bin'))
self.fix_dependencies_in_lib(join(self.contents_dir, 'MacOS', 'kitty'))
for f in glob.glob(join(self.contents_dir, '*.app', 'Contents', 'MacOS', '*')):
if not os.path.islink(f):
self.fix_dependencies_in_lib(f)
for f in walk(pdir):
if f.endswith('.so') or f.endswith('.dylib'):
self.fix_dependencies_in_lib(f)

View File

@@ -1 +1 @@
to_vm_excludes '/dependencies /build /dist /kitty/launcher/kitty* /.build-cache /.cache /.mypy_cache /.ruff_cache /tags __pycache__ /*_commands.json *.so *.pyd *.pyc *_generated.go'
to_vm_excludes '/dependencies /build /dist /kitty/launcher/kitty* /.build-cache /tags __pycache__ /*_commands.json *.so *.pyd *.pyc *_generated.go'

View File

@@ -1,302 +1,301 @@
[
{
"name": "zlib 1.3.1",
"name": "zlib",
"unix": {
"file_extension": "tar.xz",
"filename": "zlib-1.3.1.tar.xz",
"hash": "sha256:38ef96b8dfe510d42707d9c781877914792541133e1870841463bfa73f883e32",
"urls": ["https://zlib.net/{filename}"]
}
},
{
"name": "bzip2 1.0.8",
"name": "bzip2",
"os": "linux",
"unix": {
"file_extension": "tar.gz",
"filename": "bzip2-1.0.8.tar.gz",
"hash": "sha256:ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269",
"urls": ["https://www.sourceware.org/pub/{name}/{filename}"]
"urls": ["https://www.sourceware.org/pub/bzip2/bzip2-latest.tar.gz"]
}
},
{
"name": "pkg-config 0.29.2",
"type": "build",
"name": "pkg-config",
"os": "macos",
"unix": {
"file_extension": "tar.gz",
"filename": "pkg-config-0.29.2.tar.gz",
"hash": "sha256:6fc69c01688c9458a57eb9a1664c9aba372ccda420a02bf4429fe610e7e7d591",
"urls": ["https://pkg-config.freedesktop.org/releases/{filename}"]
}
},
{
"name": "openssl 3.5.4",
"name": "openssl",
"unix": {
"file_extension": "tar.gz",
"hash": "sha256:967311f84955316969bdb1d8d4b983718ef42338639c621ec4c34fddef355e99",
"filename": "openssl-3.3.0.tar.gz",
"hash": "sha256:53e66b043322a606abf0087e7699a0e033a37fa13feb9742df35c3a33b18fb02",
"urls": ["https://www.openssl.org/source/{filename}"]
}
},
{
"name": "cmake 3.29.3",
"type": "build",
"name": "cmake",
"os": "macos",
"unix": {
"file_extension": "tar.gz",
"filename": "cmake-3.29.3.tar.gz",
"hash": "sha256:252aee1448d49caa04954fd5e27d189dd51570557313e7b281636716a238bccb",
"urls": ["https://cmake.org/files/v{version_except_last}/{filename}"]
"urls": ["https://cmake.org/files/v3.19/{filename}"]
}
},
{
"name": "expat 2.6.2",
"name": "expat",
"unix": {
"file_extension": "tar.xz",
"filename": "expat-2.6.2.tar.xz",
"hash": "sha256:ee14b4c5d8908b1bec37ad937607eab183d4d9806a08adee472c3c3121d27364",
"urls": ["https://github.com/libexpat/libexpat/releases/download/R_{version_with_underscores}/{filename}"]
"urls": ["https://github.com/libexpat/libexpat/releases/download/R_2_6_2/{filename}"]
}
},
{
"name": "libxml2 2.14.6",
"name": "libxml2",
"unix": {
"file_extension": "tar.xz",
"hash": "sha256:7ce458a0affeb83f0b55f1f4f9e0e55735dbfc1a9de124ee86fb4a66b597203a",
"urls": ["https://download.gnome.org/sources/libxml2/{version_except_last}/{filename}"]
"filename": "libxml2-2.12.7.tar.xz",
"hash": "sha256:24ae78ff1363a973e6d8beba941a7945da2ac056e19b53956aeb6927fd6cfb56",
"urls": ["https://download.gnome.org/sources/libxml2/2.12/{filename}"]
}
},
{
"name": "xkbcommon 1.7.0",
"name": "xkbcommon",
"os": "linux",
"unix": {
"file_extension": "tar.xz",
"filename": "libxkbcommon-1.7.0.tar.xz",
"hash": "sha256:65782f0a10a4b455af9c6baab7040e2f537520caa2ec2092805cdfd36863b247",
"urls": ["https://xkbcommon.org/download/lib{filename}"]
"urls": ["https://xkbcommon.org/download/{filename}"]
}
},
{
"name": "sqlite 3.50.4",
"name": "sqlite",
"unix": {
"file_extension": "tar.gz",
"hash": "sha256:a3db587a1b92ee5ddac2f66b3edb41b26f9c867275782d46c3a088977d6a5b18",
"urls": ["https://www.sqlite.org/2025/{name}-autoconf-3500400.{file_extension}"]
"filename": "sqlite-autoconf-3450300.tar.gz",
"hash": "sha256:b2809ca53124c19c60f42bf627736eae011afdcc205bb48270a5ee9a38191531",
"urls": ["https://www.sqlite.org/2024/{filename}"]
}
},
{
"name": "libffi 3.4.6",
"name": "libffi",
"os": "linux",
"unix": {
"file_extension": "tar.gz",
"filename": "libffi-3.4.6.tar.gz",
"hash": "sha256:b0dea9df23c863a7a50e825440f3ebffabd65df1497108e5d437747843895a4e",
"urls": ["https://github.com/libffi/libffi/releases/download/v{version}/{filename}"]
"urls": ["https://github.com/libffi/libffi/releases/download/v3.4.6/{filename}"]
}
},
{
"name": "ncurses 6.5",
"name": "ncurses",
"os": "linux",
"unix": {
"file_extension": "tar.gz",
"filename": "ncurses-6.5.tar.gz",
"hash": "sha256:136d91bc269a9a5785e5f9e980bc76ab57428f604ce3e5a5a90cebc767971cc6",
"urls": ["https://ftp.gnu.org/gnu/ncurses/{filename}"]
}
},
{
"name": "readline 8.2",
"name": "readline",
"os": "linux",
"unix": {
"file_extension": "tar.gz",
"filename": "readline-8.2.tar.gz",
"hash": "sha256:3feb7171f16a84ee82ca18a36d7b9be109a52c04f492a053331d7d1095007c35",
"urls": ["https://ftp.gnu.org/gnu/readline/{filename}"]
}
},
{
"name": "xz 5.8.1",
"name": "xz",
"unix": {
"file_extension": "tar.gz",
"hash": "sha256:507825b599356c10dca1cd720c9d0d0c9d5400b9de300af00e4d1ea150795543",
"filename": "xz-5.2.5.tar.gz",
"hash": "sha256:f6f4910fd033078738bd82bfba4f49219d03b17eb0794eb91efbae419f4aba10",
"urls": ["https://tukaani.org/xz/{filename}"]
}
},
{
"name": "libxxhash 0.8.2",
"name": "libxxhash",
"unix": {
"file_extension": "tar.gz",
"filename": "xxHash-0.8.2.tar.gz",
"hash": "sha256:baee0c6afd4f03165de7a4e67988d16f0f2b257b51d0e3cb91909302a26a79c4",
"urls": ["https://github.com/Cyan4973/xxHash/archive/refs/tags/v{version}.{file_extension}"]
"urls": ["https://github.com/Cyan4973/xxHash/archive/refs/tags/v0.8.2.tar.gz"]
}
},
{
"name": "xcrypt 4.4.36",
"name": "xcrypt",
"os": "linux",
"unix": {
"file_extension": "tar.gz",
"filename": "xcrypt-4.4.36.tar.gz",
"hash": "sha256:b979838d5f1f238869d467484793b72b8bca64c4eae696fdbba0a9e0b6c28453",
"urls": ["https://github.com/besser82/libxcrypt/archive/v{version}.{file_extension}"]
"urls": ["https://github.com/besser82/libxcrypt/archive/v4.4.36.tar.gz"]
}
},
{
"name": "python 3.12.12",
"name": "python",
"unix": {
"file_extension": "tar.xz",
"hash": "sha256:fb85a13414b028c49ba18bbd523c2d055a30b56b18b92ce454ea2c51edc656c4",
"urls": ["https://www.python.org/ftp/python/{version}/Python-{version}.{file_extension}"]
"filename": "Python-3.12.3.tar.xz",
"hash": "sha256:56bfef1fdfc1221ce6720e43a661e3eb41785dd914ce99698d8c7896af4bdaa1",
"urls": ["https://www.python.org/ftp/python/3.12.3/{filename}"]
}
},
{
"name": "libpng 1.6.52",
"name": "libpng",
"unix": {
"file_extension": "tar.xz",
"hash": "sha256:36bd726228ec93a3b6c22fdb49e94a67b16f2fe9b39b78b7cb65772966661ccc",
"filename": "libpng-1.6.43.tar.xz",
"hash": "sha256:6a5ca0652392a2d7c9db2ae5b40210843c0bbc081cbd410825ab00cc59f14a6c",
"urls": ["https://downloads.sourceforge.net/sourceforge/libpng/{filename}"]
}
},
{
"name": "lcms2 2.17",
"name": "lcms2",
"unix": {
"file_extension": "tar.gz",
"hash": "sha256:d11af569e42a1baa1650d20ad61d12e41af4fead4aa7964a01f93b08b53ab074",
"urls": ["https://github.com/mm2/Little-CMS/releases/download/lcms{version}/lcms2-{version}.{file_extension}"]
"filename": "lcms2-2.16.tar.gz",
"hash": "sha256:d873d34ad8b9b4cea010631f1a6228d2087475e4dc5e763eb81acc23d9d45a51",
"urls": ["https://github.com/mm2/Little-CMS/archive/2.16/{filename}"]
}
},
{
"name": "graphite 1.3.14",
"name": "graphite",
"os": "linux",
"unix": {
"file_extension": "tgz",
"filename": "graphite2-1.3.14.tgz",
"hash": "sha256:f99d1c13aa5fa296898a181dff9b82fb25f6cc0933dbaa7a475d8109bd54209d",
"urls": ["https://downloads.sourceforge.net/silgraphite/graphite2-{version}.{file_extension}"]
"urls": ["https://downloads.sourceforge.net/silgraphite/{filename}"]
}
},
{
"name": "pcre 10.43",
"name": "pcre",
"os": "linux",
"unix": {
"file_extension": "tar.bz2",
"filename": "pcre2-10.43.tar.bz2",
"hash": "sha256:e2a53984ff0b07dfdb5ae4486bbb9b21cca8e7df2434096cc9bf1b728c350bcb",
"urls": ["https://github.com/PCRE2Project/pcre2/releases/download/pcre2-10.43/pcre2-{version}.{file_extension}"]
"urls": ["https://github.com/PCRE2Project/pcre2/releases/download/pcre2-10.43/{filename}"]
}
},
{
"name": "iconv 1.17",
"name": "iconv",
"os": "linux",
"unix": {
"file_extension": "tar.gz",
"filename": "libiconv-1.17.tar.gz",
"hash": "sha256:8f74213b56238c85a50a5329f77e06198771e70dd9a739779f4c02f65d971313",
"urls": ["https://ftp.gnu.org/pub/gnu/libiconv/libiconv-{version}.{file_extension}"]
"urls": ["https://ftp.gnu.org/pub/gnu/libiconv/{filename}"]
}
},
{
"name": "glib 2.82.5",
"os": "linux",
"name": "installer",
"comment": "Needed infrastructure for installing pure python packages (wheels)",
"unix": {
"file_extension": "tar.xz",
"hash": "sha256:05c2031f9bdf6b5aba7a06ca84f0b4aced28b19bf1b50c6ab25cc675277cbc3f",
"urls": ["https://download.gnome.org/sources/glib/{version_except_last}/{filename}"]
"filename": "installer-0.7.0-py3-none-any.whl",
"hash": "sha256:05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53",
"urls": ["pypi"]
}
},
{
"name": "libbrotli 1.1.0",
"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",
"os": "linux",
"unix": {
"file_extension": "tar.gz",
"filename": "glib-2.80.2.tar.xz",
"hash": "sha256:b9cfb6f7a5bd5b31238fd5d56df226b2dda5ea37611475bf89f6a0f9400fe8bd",
"urls": ["https://download.gnome.org/sources/glib/2.80/{filename}"]
}
},
{
"name": "brotli",
"os": "linux",
"unix": {
"filename": "brotli-1.1.0.tar.gz",
"hash": "sha256:e720a6ca29428b803f4ad165371771f5398faba397edf6778837a18599ea13ff",
"urls": ["https://github.com/google/brotli/archive/v{version}/{filename}"]
"urls": ["https://github.com/google/brotli/archive/v1.1.0/{filename}"]
}
},
{
"name": "pixman 0.44.2",
"name": "freetype",
"os": "linux",
"unix": {
"file_extension": "tar.xz",
"hash": "sha256:50baf820dde0c5ff9714d03d2df4970f606a3d3b1024f5404c0398a9821cc4b0",
"urls": ["https://www.cairographics.org/releases/{filename}"]
}
},
{
"name": "freetype 2.13.2",
"os": "linux",
"unix": {
"file_extension": "tar.xz",
"filename": "freetype-2.13.2.tar.xz",
"hash": "sha256:12991c4e55c506dd7f9b765933e62fd2be2e06d421505d7950a132e4f1bb484d",
"urls": ["https://download.savannah.gnu.org/releases/freetype/{filename}"]
}
},
{
"name": "fontconfig 2.13.1",
"name": "fontconfig",
"os": "linux",
"unix": {
"file_extension": "tar.bz2",
"filename": "fontconfig-2.13.1.tar.bz2",
"hash": "sha256:f655dd2a986d7aa97e052261b36aa67b0a64989496361eca8d604e6414006741",
"urls": ["https://www.fontconfig.org/release/{filename}"]
}
},
{
"name": "cairo 1.18.2",
"os": "linux",
"name": "harfbuzz",
"unix": {
"file_extension": "tar.xz",
"hash": "sha256:a62b9bb42425e844cc3d6ddde043ff39dbabedd1542eba57a2eb79f85889d45a",
"urls": ["https://www.cairographics.org/releases/{filename}"]
}
},
{
"name": "harfbuzz 8.5.0",
"unix": {
"file_extension": "tar.xz",
"filename": "harfbuzz-8.5.0.tar.xz",
"hash": "sha256:77e4f7f98f3d86bf8788b53e6832fb96279956e1c3961988ea3d4b7ca41ddc27",
"urls": ["https://github.com/harfbuzz/harfbuzz/releases/download/{version}/{filename}"]
"urls": ["https://github.com/harfbuzz/harfbuzz/releases/download/8.5.0/{filename}"]
}
},
{
"name": "simde 0.7.6",
"name": "simde",
"comment": "Cannot update till gcc in the build VM is updated as simde 0.8 requires newer gcc",
"unix": {
"file_extension": "tar.xz",
"filename": "simde-amalgamated-0.7.6.tar.xz",
"hash": "sha256:703eac1f2af7de1f7e4aea2286130b98e1addcc0559426e78304c92e2b4eb5e1",
"urls": ["https://github.com/simd-everywhere/simde/releases/download/v{version}/simde-amalgamated-{version}.{file_extension}"]
"urls": ["https://github.com/simd-everywhere/simde/releases/download/v0.7.6/{filename}"]
}
},
{
"name": "wayland 1.24.0",
"name": "wayland",
"os": "linux",
"unix": {
"file_extension": "tar.xz",
"hash": "sha256:82892487a01ad67b334eca83b54317a7c86a03a89cfadacfef5211f11a5d0536",
"urls": ["https://gitlab.freedesktop.org/wayland/wayland/-/releases/{version}/downloads/{filename}"]
"filename": "wayland-1.23.0.tar.xz",
"hash": "sha256:05b3e1574d3e67626b5974f862f36b5b427c7ceeb965cb36a4e6c2d342e45ab2",
"urls": ["https://gitlab.freedesktop.org/wayland/wayland/-/releases/1.23.0/downloads/{filename}"]
}
},
{
"name": "wayland-protocols 1.45",
"name": "wayland-protocols",
"os": "linux",
"unix": {
"file_extension": "tar.xz",
"hash": "sha256:4d2b2a9e3e099d017dc8107bf1c334d27bb87d9e4aff19a0c8d856d17cd41ef0",
"urls": ["https://gitlab.freedesktop.org/wayland/wayland-protocols/-/releases/{version}/downloads/{filename}"]
"filename": "wayland-protocols-1.36.tar.xz",
"hash": "sha256:71fd4de05e79f9a1ca559fac30c1f8365fa10346422f9fe795f74d77b9ef7e92",
"urls": ["https://gitlab.freedesktop.org/wayland/wayland-protocols/-/releases/1.36/downloads/{filename}"]
}
}
]

View File

@@ -13,6 +13,6 @@ for attr in ('linguist-generated', 'linguist-vendored'):
fname = line.split(':', 1)[0]
all_files.discard(fname)
all_files -= {'gen/rowcolumn-diacritics.txt'}
all_files -= {'gen/nerd-fonts-glyphs.txt', 'gen/rowcolumn-diacritics.txt'}
cp = subprocess.run(['cloc', '--list-file', '-'], input='\n'.join(all_files).encode())
raise SystemExit(cp.returncode)

View File

@@ -25,7 +25,6 @@ Previous shell prompt :sc:`scroll_to_previous_prompt` (see :ref:`shell_int
Next shell prompt :sc:`scroll_to_next_prompt` (see :ref:`shell_integration`)
Browse scrollback in less :sc:`show_scrollback`
Browse last cmd output :sc:`show_last_command_output` (see :ref:`shell_integration`)
Search scrollback in less :sc:`search_scrollback` (also :kbd:`⌘+F` on macOS)
========================= =======================
The scroll actions only take effect when the terminal is in the main screen.

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
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`
binary as well. Whichever folder you choose to create the symlink in should
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.
binary as well.
Manually installing

View File

@@ -13,8 +13,7 @@ Build from source
|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
<https://go.dev/doc/install>`__ (on Linux, the :ref:`X11 development libraries <x11-dev-libs>` as well).
After installing those, run the following commands::
<https://go.dev/doc/install>`__. After installing those, run the following commands::
git clone https://github.com/kovidgoyal/kitty.git && cd kitty
./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
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
are X11 and DBUS on Linux.
to use these rather than system libraries.
If you make changes to kitty code, simply re-run :code:`./dev.sh build`
to build kitty with your changes.
@@ -89,8 +87,6 @@ Run-time dependencies:
* ``liblcms2``
* ``libxxhash``
* ``openssl``
* ``pixman`` (not needed on macOS)
* ``cairo`` (not needed on macOS)
* ``freetype`` (not needed on macOS)
* ``fontconfig`` (not needed on macOS)
* ``libcanberra`` (not needed on macOS)
@@ -109,18 +105,6 @@ Build-time dependencies:
need to install the following packages, if they are not already installed by
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``
- ``libxcursor-dev``
- ``libxrandr-dev``
@@ -130,7 +114,11 @@ Build-time dependencies:
- ``libxkbcommon-x11-dev``
- ``libfontconfig-dev``
- ``libx11-xcb-dev``
- ``liblcms2-dev``
- ``libssl-dev``
- ``libpython3-dev``
- ``libxxhash-dev``
- ``libsimde-dev``
Build and run from source with Nix

View File

@@ -9,81 +9,6 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
Recent major new features
---------------------------
Choose files, fast [0.45]
~~~~~~~~~~~~~~~~~~~~~~~~~~~
A new :doc:`kitten to select files at the speed of thought
</kittens/choose-files>` with a keyboard first interface and support for
content previews of text file with syntax highlighting, images, videos, e-books
and more. Allows you to select files for use at the shell prompt or other
terminal workflows with just a few keystrokes, similar to how fuzzy finders
like `fzf <https://github.com/junegunn/fzf/>`__ operate, but with designed for
files in particular.
On Linux, it can even be used as a :doc:`drop in replacement </kittens/desktop-ui>`
for the File Open/Save dialog boxes in GUI programs.
Sessions [0.43]
~~~~~~~~~~~~~~~~
kitty has long had support for :doc:`sessions`, aka simple text files where you
can define what tabs, windows and programs you wish to run in kitty. Now in
addition to that kitty has the ability to :ref:`create and switch between
sessions <goto_session>` with a single keypress and also to manually setup some
tabs/windows in kitty and :ref:`save it as a session file <complex_sessions>`,
for seamless and intuitive session file creation.
A scrollbar for the kitty scrollback [0.43]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A long requested feature, kitty has finally :pull:`gotten a scrollbar <8945>`
that can be used with the mouse for browsing its scrollback. The bar appear
automatically when you start scrolling backwards and is :opt:`extensively
configurable <scrollbar>` in kitty.conf. Note that the old ``scrollback_indicator_opacity``
option is deprecated.
Multiple cursors [0.43]
~~~~~~~~~~~~~~~~~~~~~~~~
kitty has pioneered a new :doc:`escape code protocol
<multiple-cursors-protocol>` that allows terminal applications to use multiple
cursors, rendered natively. These are typically used in editors to make the
same edit at multiple locations. Now terminal based editors can use properly
rendered native cursors, just like their GUI cousins, at last.
Access kitty with a single keypress [0.42]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. include:: quake-screenshots.rst
kitty now has a Quake like floating, translucent terminal window, so you can access
all that kitty goodness instantly with a single keypress.
See the screenshots on the side and head over to the :doc:`kitten page for details
on how to set it up </kittens/quick-access-terminal>`.
Multiple sized text [0.40]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -149,621 +74,7 @@ consumption to do the same tasks.
Detailed list of changes
-------------------------------------
0.45.0 [2025-12-24]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- A new :doc:`kitten to select files at the speed of thought </kittens/choose-files>` with a keyboard first interface and support for content previews of text file with syntax highlighting, images, videos, e-books and more (:iss:`9263`)
- Add support for the `paste events protocol <https://rockorager.dev/misc/bracketed-paste-mime/>`__ (:iss:`9183`)
- icat kitten: Add support for animated PNG and animated WebP, netPBM images, ICC color profiles and CCIP color space metadata to the builtin engine
- icat kitten: Add a new flag :option:`kitty +kitten icat --fit` to control how images are scaled to fit the screen (:iss:`9201`)
- icat kitten: The :option:`kitty +kitten icat --scale-up` flag now takes effect when not using :option:`kitty +kitten icat --place` as well
- Add a mappable action :ac:`copy_last_command_output` to copy the output of the last
command to the clipboard (:pull:`9185`)
- ssh kitten: Fix a bug where automatic login was not working (:iss:`9187`)
- Graphics: Fix overwrite composition mode for animation frames not being honored
- Automatic color scheme switching: Fix title bar and scroll bar colors not being updated (:iss:`9167`)
- macOS: Fix cycle through OS windows only swapping between the two most recent
OS Windows. Also add a cycle through OS Windows backwards action.
(:iss:`9215`)
- :ac:`goto_session`: allow specifying a directory to select a session file
from the directory (:pull:`9219`)
- Have reloading config also reload the custom tab bar python modules (:disc:`9221`)
- kitten @ ls: Also output the neighbors for every window (:disc:`9225`)
- Have the :option:`kitty --start-as` flag be respected when used with
:option:`kitty --single-instance` (:iss:`9228`)
- When expanding environment variables in :opt:`listen_on` allow the :opt:`env`
directive to take effect
- macOS: Fix closing an OS Window when another OS Window is minimized causing
the minimized window to be un-minimized (:iss:`8913`)
- Do not rewrap the text in the alternate screen buffer. Avoids flicker during
live resize with no :opt:`resize_debounce_time` (:disc:`9142`)
- Add a default mapping :ac:`search_scrollback` to open the scrollback in a
pager in search mode. If any text is currently selected it is automatically
searched for.
- Wayland: Fix spurious key repeat events when some user defined callback takes
a long time to execute (:iss:`9224`)
- When moving windows to a new tab/OS Window fix overlay windows not being
grouped with their parent windows (:iss:`9266`)
- Linux: Fix a bug causing colors to occasionally all go black when using mesa
>= 25.3.0 with nouveau GPU driver (:iss:`9235`)
- Fix :opt:`tab_bar_min_tabs` not respecting :opt:`tab_bar_filter` (:iss:`9278`)
- macOS: Workaround for regression in Tahoe 26.2 that breaks :option:`kitty --detach`
(:iss:`9288`)
- macOS: Workaround for yet another Tahoe regression causing macOS to start an
AutoFill helper process and not shut it down on application exit (:iss:`9299`)
0.44.0 [2025-11-03]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Allow kitty to read a specified set of environment variables from your
login shell at startup using the :opt:`env` directive in kitty.conf
(:iss:`9042`)
- A new option :opt:`draw_window_borders_for_single_window` to force kitty to
always draw a window border even when only a single window is present
(:pull:`9112`)
- Fix a regression in 0.43.0 that caused a black flicker when closing a tab in
the presence of a background image (:iss:`9060`)
- Further improvements to rounded corner rendering, especially at low DPI
(:pull:`9091`)
- Splits layout: Fix a bug that could cause a corrupted layout in some
circumstances (:iss:`9059`)
- Fix a regression in the previous release that broke ``goto_session -1``
- Fix rendering broken on ancient GPU drivers that do not support rendering to 16 bit textures (:iss:`9068`)
- Fix tab bar sometimes showing incorrect tabs when it is filtered to show only
tabs from the current session (:iss:`9079`)
- macOS: Workaround for bug in macOS Tahoe that caused OS Windows that are
fullscreen to crash kitty when returning from sleep on some machines (:iss:`8983`)
- Graphics: Fix animated images sometimes not auto playing or auto playing at the wrong start frame if the same image id is used for a subsequent image
- Fix a regression in 0.43.0 that caused high CPU usage when :opt:`disable_ligatures` was set to ``cursor`` and the tab bar was visible (:iss:`9071`)
- macOS: Handle dropping of file promises into kitty in addition to file paths (:pull:`9084`)
- macOS: Fix indeterminate progress bar displayed on dock icon increasing speed when indeterminate progress is set without being cleared first (:iss:`9114`)
- macOS: Performance and power usage improvements of about 5-10% (:pull:`9131`)
- macOS: Add an item to the global menu to Cycle through OS windows
- macOS: Quick access terminal: Fix a crash when changing font size (:iss:`9178`)
- Wayland: Fix ``center-sized`` panels not working on smithay based compositors (:pull:`9117`)
- Wayland: Fix scrolling using some mouse wheels that produce "VALUE120" based
scroll events too fast on some compositors (:pull:`9128`)
- Add support for Unicode 17
- Fix a regression in 0.43.0 that caused :opt:`tab_bar_margin_width` to be
doubled on the right edge of the tab bar (:iss:`9154`)
- Session files: Add a new ``focus_tab`` command to specify which tab should be
active when a session is loaded. Accepts either a plain number (0-based index)
or a match expression for flexible tab selection, allowing sessions to preserve
the active tab state (:doc:`sessions`)
- :ac:`save_as_session`: Add ``--base-dir`` option to specify a base directory
for saving session files with relative paths, useful when the current working
directory is not the desired location (:doc:`sessions`)
- Add ``state:focused_os_window`` match query to select all windows in the
currently focused OS window (:ref:`search_syntax`)
- Session saving now preserves visual tab order and active tab rather than tab
activation history as this is generally more important. In the future may
have it save tab history as well (:pull:`9163`)
- The :sc:`reset_terminal` shortcut to reset the terminal now also resets termios state
0.43.1 [2025-10-01]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- ssh kitten: Allow specifying a password and/or TOTP authentication secret to
automate interactive logins in scenarios where public key authentication is
not supported (:pull:`9020`)
- macOS: Fix a bug where the color of a transparent titlebar was off when
running in the release build versus the build from source. Also fix using a
transparent titlebar causing the background opacity to be doubled.
- Fix a regression in the previous release that caused the incorrect tab to be
active when loading a session (:iss:`9025`)
- macOS: Workaround for bug in macOS Tahoe that caused closed OS Windows to
remain as invisible rectangles that intercept mouse events (:iss:`8952`)
- macOS: Fix a regression in the previous release that broke automatic
switching of dark/light mode when setting :opt:`macos_titlebar_color` to an
arbitrary color (:iss:`9034`)
- :ac:`goto_session`: Add ``--sort-by=alphabetical`` to have the interactive session
picker list the sessions in a fixed order rather than by most recent
(:disc:`9033`)
- Fix a regression in the previous release that caused the cursor trail to not
be hidden properly (:iss:`9039`)
- Session files: Fix a regression in the previous release that broke matching on
windows in the current tab (:iss:`9037`)
- Fix a regression in the previous release that broke clearing screen lines
when in margin mode (:iss:`9049`)
0.43.0 [2025-09-28]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- New support for creating and switching to :doc:`sessions` easily, allowing
users to define and use sessions/projects efficiently (:iss:`8911`)
- Add a configurable :opt:`scrollbar` for the kitty scrollback (:pull:`8945`)
- A new protocol for :doc:`multiple cursors <multiple-cursors-protocol>` in the terminal (:iss:`8927`)
- macOS: Allow the window title bar to be semi-transparent when
:opt:`background_opacity` is less than one and :opt:`macos_titlebar_color` is
set to ``background`` (:pull:`8906`)
- A new :opt:`cursor_trail_color` setting to independently control the color of
cursor trails (:pull:`8830`)
- macOS: Add the default :kbd:`Cmd+L` mapping from Terminal.app to erase the
last command and its output (:disc:`6040`)
- Fix :opt:`background_opacity` being non-linear especially with light color themes.
Note that this might require you to adjust the value of this setting to get
back your current look. (:iss:`8869`)
- **backward incompatibility**: :opt:`background_opacity` no longer applies to
:opt:`background_image` instead add an alpha channel to the image itself
- Add support for blinking text. Text marked as blinking now blinks in exact
rhythm with the cursor. The blinking animation and max duration are
controlled by :opt:`cursor_blink_interval` and
:opt:`cursor_stop_blinking_after`. (:pull:`8551`)
- Allow using a custom python function to draw tab titles in the tab bar, see
:opt:`tab_title_template`
- Wayland: Fix incorrect window size calculation when transitioning from
full screen to non-full screen with client side decorations (:iss:`8826`)
- macOS: Fix hiding quick access terminal window not restoring focus to
previously active application (:disc:`8840`)
- macOS: Fix showing the quick access terminal on a space other than the space
it was last active on, after full screening some application causes the quick
access terminal to appear on the old space (:iss:`8740`)
- macOS: When toggling open the quick access terminal move it to the currently
active monitor (the monitor with the mouse pointer on it) (:iss:`9003`)
- macOS: Fix closing an OS Window when another OS Window is minimized causing
the minimized window to be un-minimized (:iss:`8913`)
- Allow using backspace to move the cursor onto the previous line in cooked
mode. This is indicated by the `bw` property in kitty's terminfo
(:iss:`8841`)
- Watchers: A new event for global watchers corresponding to the tab bar being changed (:disc:`8842`)
- Fix a regression in 0.40.0 that broke handling of the VS16 variation selector
when it caused a character to flow to the next line (:iss:`8848`)
- Fix rendering of underlines when using larger text sizes with the space and
en-space characters (:iss:`8950`)
- Fix updating panel configuration on visibility toggle and via remote control
not working (:iss:`8984`)
- Improve rendering of rounded rectangles (:pull:`9000`)
- Wayland: Update bundled copy of libwayland to 1.24 from 1.23.1 because the
just released mesa 25.2.0 breaks with libwayland < 1.24 (:iss:`8884`)
- macOS: Pass the :kbd:`Cmd+C` shortcut to the application running in the
terminal when no text is selected (:pull:`8946`)
- macOS: Workaround for bug in macOS Tahoe that caused OS Windows that are
fullscreen on a monitor that is disconnected while macOS is asleep to crash kitty (:iss:`8983`)
0.42.2 [2025-07-16]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- A new :ref:`protocol extension <mouse_leave_window>` to notify terminal programs that have turned on SGR Pixel mouse reporting when the mouse leaves the window (:disc:`8808`)
- clipboard kitten: Can now optionally take a password to avoid repeated
permission prompts when accessing the clipboard. Based on a
:ref:`protocol extension <clipboard_repeated_permission>`. (:iss:`8789`)
- A new :option:`launch --hold-after-ssh` to not close a launched window
that connects directly to a remote host because of
:option:`launch --cwd`:code:`=current` when the connection ends (:pull:`8807`)
- Fix :opt:`remember_window_position` not working because of a stupid typo (:iss:`8646`)
- A new :option:`kitty --grab-keyboard` that can be used to grab the keyboard so that global shortcuts are sent to kitty instead
- Remote control: Fix holding a remote control socket open causing the kitty I/O thread to go into a loop and not respond on other remote control sockets (:disc:`8670`)
- hints kitten: Preserve line breaks when the hint is over a line break (:iss:`8674`)
- Fix a segfault when using the :ac:`copy_ansi_to_clipboard` action (:iss:`8682`)
- Fix a crash when using linear easing curves for animations (:iss:`8692`)
- Graphics protocol: Add a note clarifying image update behavior on re-transmission (:iss:`8701`)
- Wayland GNOME: Fix incorrect OS Window tracking because GNOME has started
activating windows on non-current workspaces (:iss:`8716`)
- Fix a regression in 0.40.0 that broke rendering of VS15 variation selectors in some circumstances (:iss:`8731`, :iss:`8794`)
- Fix a regression in 0.40.0 that broke serialization of tab characters as ANSI text (:iss:`8741`)
- Fix a regression in 0.40.0 that broke erasing of characters in a line in the presence of wide characters (:iss:`8758`)
- Fix a regression in 0.40.0 that broke hyperlinking of wide characters (:iss:`8796`)
- Fix a regression that broke using :kbd:`esc` to exit visual select window mode (:iss:`8767`)
- kitten run-shell: Fix SIGINT blocked when execing the shell (:iss:`8754`)
0.42.1 [2025-05-17]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Fix ambiguous width and private use characters not being rendered when used with variable width text-sizing protocol escape codes
- Quick access terminal: Restore focus to previously active window when hiding the quick access terminal window on macOS (:iss:`8627`)
- Wayland: Fix an abort if the terminal program sets a window title longer than 2KB that contains CSI escape sequences and multibyte UTF-8 (:iss:`8619`)
- Quick access terminal: Allow toggling the window to full screen using the standard kitty :sc:`toggle_fullscreen` shortcut (:iss:`8626`)
- Quick access terminal: Allow configuring the monitor to display the panel on in Wayland/X11 (:iss:`8630`)
- A new setting :opt:`remember_window_position` to optionally use the position of the last closed kitty OS Window as the position of the first kitty OS Window when running a new kitty instance (:pull:`8601`)
- Panel kitten: A new ``center-sized`` value for :option:`--edge <kitty +kitten panel --edge>` to allow easily creating sized and centered panels
- Wayland: The `kitty --name` flag now sets the XDG *window tag* on compositors
that support the `xdg-toplevel-tag <https://wayland.app/protocols/xdg-toplevel-tag-v1>`__ protocol.
0.42.0 [2025-05-11]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- A new kitten: :doc:`quick-access-terminal </kittens/quick-access-terminal>` to :ref:`quake`
- The :doc:`panel kitten </kittens/panel>` works on macOS and X11 as well as Wayland (:iss:`2590`)
- **Behavior change**: Now kitty does full grapheme segmentation following the
Unicode 16 spec when splitting text into cells (:iss:`8533`)
- **Behavior change**: The :ref:`automatic color switching functionality <auto_color_scheme>` now also controls background image settings (:iss:`8603`)
- panel kitten: Allow using :option:`kitty +kitten panel --single-instance` to create multiple panels in one process (:iss:`8549`)
- launch: Allow creating desktop panels such as those created by the :doc:`panel kitten </kittens/panel>` (:iss:`8549`)
- Remote control: Allow modifying desktop panels and showing/hiding OS Windows
using the ``kitten @ resize-os-window`` command (:iss:`8550`)
- Remote control launch: Allow waiting for a program launched in a new window
to exit and get the exit code via the `kitty +launch
--wait-for-child-to-exit` command line flag (:disc:`8573`)
- Allow starting kitty with the OS window hidden via :option:`kitty --start-as=hidden <kitty --start-as>`, useful for single instance mode (:iss:`3466`)
- 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`)
- diff kitten: Allow diffing named pipes (:iss:`8597`)
- 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
- panel kitten: Allow specifying panel size in pixels in addition to cells
- Fix a regression in 0.36.0 that caused using = with single letter command
line flags to no longer work correctly (:iss:`8556`)
- Single instance: Preserve environment variables from invoking environment in
newly created window (:disc:`8567`)
- Single instance: Reset OS Window class and name in new single instance OS
windows (:disc:`8567`)
- macOS: Fix text color in visual window select ignoring the color theme (:iss:`8579`)
- Launch action: Allow using an env var that resolves to a full command-line as the program to launch (:pull:`8613`)
- :ac:`change_font_size` allow multiplying/dividing the current font size in addition to incrementing it (:iss:`8616`)
- Box drawing: Improve appearance of rounder corners, giving them a uniform line width (:iss:`8299`)
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]
0.36.3 [future]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- 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`)
@@ -782,10 +93,6 @@ Detailed list of changes
- 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]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -894,7 +201,7 @@ Detailed list of changes
- Wayland labwc: Fix kitty timing out waiting for compositor to quit fucking around with scales on labwc (:iss:`7540`)
- Fix ``scrollback_indicator_opacity`` not actually controlling the opacity (:iss:`7557`)
- Fix :opt:`scrollback_indicator_opacity` not actually controlling the opacity (:iss:`7557`)
- URL detection: Fix IPv6 hostnames breaking URL detection (:iss:`7565`)
@@ -978,7 +285,7 @@ Detailed list of changes
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`)
- Show a small scrollback indicator 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`)
- Wayland: Support fractional scales so that there is no wasted drawing at larger scale followed by resizing in the compositor
@@ -2843,7 +2150,7 @@ Detailed list of changes
do not display a box around active windows
- Add a new extensible escape code to allow terminal programs to trigger
desktop notifications. See :ref:`notifications_on_the_desktop` (:iss:`1474`)
desktop notifications. See :ref:`desktop_notifications` (:iss:`1474`)
- Implement special rendering for various characters from the set of "Symbols
for Legacy Computing" from the Unicode 13 standard

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)
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
is the size of a chunk *before* base64 encoding). All
for an individual type. A recommended size for each chunk is 4096 bytes. All
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
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
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).
All the chunks for a given MIME type must be sent sequentially, before
bytes. 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
terminal replies with a single packet indicating success::
@@ -138,72 +136,6 @@ the data, but create multiple references to it in the system clipboard. Alias
packets can be sent anytime after the initial write packet and before the end
of data packet.
.. _clipboard_repeated_permission:
Avoiding repeated permission prompts
--------------------------------------
.. versionadded:: 0.42.2
using a password to avoid repeated confirmations
If a program like an editor wants to make use of the system clipboard, by
default, the user is prompted on every read request. This can become quite
fatiguing. To avoid this situation, this protocol allows sending a password
and human friendly name with ``type=write`` and ``type=read`` requests. The
terminal can then ask the user to allow all future requests using that
password. If the user agrees, future requests on the same tty will be
automatically allowed by the terminal. The editor or other program using
this facility should ideally use a password randomly generated at startup,
such as a UUID4. However, terminals may implement permanent/stored passwords.
Users can then configure terminal programs they trust to use these password.
The password and the human name are encoded using the ``pw`` and ``name`` keys
in the metadata. The values are UTF-8 strings that are base64 encoded.
Specifying a password without a human friendly name is equivalent to not
specifying a password and the terminal must treat the request as though
it had no password.
Allowing terminal applications to respond to paste events
--------------------------------------------------------------
.. versionadded:: 0.44.1
paste events via the 5522 mode
If a TUI application wants to handle paste events (like the user pressing the
paste key shortcut used by the terminal or selecting paste from a terminal UI menu)
it can enable the *paste events* private mode (5522), as described in this `ancillary
specification <https://rockorager.dev/misc/bracketed-paste-mime/>`__. When that
mode is set, the terminal will send the application a list of MIME types on the
clipboard every time the user triggers a paste action. The application is then
free to request whatever MIME data it wants from the list of types.
The mode can be enabled using the standard DECSET or DECRST control sequences.
``CSI ? 5522 h`` to enable the mode. ``CSI ? 5522 l`` to disable the mode.
The terminal *should* send a one time password with the list of mime
types, as the ``pw`` key (base64 encoded). The application can then use this
password to request data from the clipboard without needing a permission
prompt. The human name *should* be set to ``Paste event`` (base64 encoded) when
the application uses this one time password.
Detecting support for this protocol
-----------------------------------------
Applications can detect if a terminal supports this protocol with a standard
DECRQM query:
.. code::
CSI ? 5522 $ p
To which the terminal will respond with a DECRPM response:
.. code::
CSI ? 5522 ; Ps $ y
A Ps value of 0 or 4 means the mode is not supported.
Support for terminal multiplexers
------------------------------------
@@ -218,15 +150,11 @@ other characters must be stripped out from the id by the terminal emulator
before retransmitting it.
Note that when using a terminal multiplexer it is possible for two different
programs to overwrite each other's clipboard requests. This is fundamentally
programs to overwrite each others clipboard requests. This is fundamentally
unavoidable since the system clipboard is a single global shared resource.
However, there is an additional complication where responses from this protocol
However, there is an additional complication where responses form this protocol
could get lost if, for instance, multiple write requests are received
simultaneously. It is up to well designed multiplexers to ensure that only a
single request is in flight at a time. The multiplexer can abort requests by
sending back the ``EBUSY`` error code indicating some other window is trying
to access the clipboard.
When the terminal sends an unsolicited paste event because the user triggered
a paste and the 5522 mode is enabled, there will be no associated id. In this
case, the multiplexer must forward the event to the currently active window.

View File

@@ -52,8 +52,8 @@ 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 (\\)``.
Here, ``<OSC>`` is the two bytes ``0x1b (ESC)`` and ``0x5b ([)``. ``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
@@ -69,7 +69,7 @@ selection_foreground The foreground color of selections
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..7 A background color that is rendered Unset
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
@@ -103,7 +103,7 @@ 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 sends to it for a query it
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.

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.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 {{{
# -- Project information -----------------------------------------------------
@@ -62,7 +62,7 @@ extensions = [
'sphinx.ext.extlinks',
'sphinx_copybutton',
'sphinx_inline_tabs',
'sphinxext.opengraph',
"sphinxext.opengraph",
]
# URL for OpenGraph tags
@@ -119,7 +119,6 @@ def go_version(go_mod_path: str) -> str: # {{{
string_replacements = {
'_kitty_install_cmd': 'curl -L https://sw.kovidgoyal.net/kitty/installer.sh | sh /dev/stdin',
'_build_go_version': go_version('../go.mod'),
'_text_size_code': str(TEXT_SIZE_CODE),
}
@@ -237,13 +236,6 @@ def write_cli_docs(all_kitten_names: Iterable[str]) -> None:
usage='file-or-dir-to-copy ...', message=copy_message
))
del sys.modules['kittens.ssh.main']
from kitty.session import save_as_session_message, save_as_session_options
with open('generated/save-as-session.rst', 'w') as f:
f.write(option_spec_as_rst(
appname='save_as_session', ospec=save_as_session_options, heading_char='^',
usage='[path-to-save-session-file-at]',
message=save_as_session_message,
))
from kitty.launch import options_spec as launch_options_spec
with open('generated/launch.rst', 'w') as f:
@@ -359,7 +351,7 @@ def write_remote_control_protocol_docs() -> None: # {{{
def replace_string(app: Any, docname: str, source: List[str]) -> None: # {{{
src = source[0]
for k, v in string_replacements.items():
for k, v in app.config.string_replacements.items():
src = src.replace(k, v)
source[0] = src
# }}}
@@ -650,10 +642,33 @@ def monkeypatch_man_writer() -> None:
'''
Monkeypatch the docutils man translator to be nicer
'''
from docutils.nodes import figure
from docutils.writers.manpage import Translator
from docutils.nodes import Element
from docutils.writers.manpage import Table, Translator
from sphinx.writers.manpage import ManualPageTranslator
# Generate nicer tables https://sourceforge.net/p/docutils/bugs/475/
class PatchedTable(Table): # type: ignore
_options: list[str]
def __init__(self) -> None:
super().__init__()
self.needs_border_removal = self._options == ['center']
if self.needs_border_removal:
self._options = ['box', 'center']
def as_list(self) -> list[str]:
ans: list[str] = super().as_list()
if self.needs_border_removal:
# remove side and top borders as we use box in self._options
ans[2] = ans[2][1:]
a, b = ans[2].rpartition('|')[::2]
ans[2] = a + b
if ans[3] == '_\n':
del ans[3] # top border
del ans[-2] # bottom border
return ans
def visit_table(self: ManualPageTranslator, node: object) -> None:
setattr(self, '_active_table', PatchedTable())
setattr(ManualPageTranslator, 'visit_table', visit_table)
# Improve header generation
def header(self: ManualPageTranslator) -> str:
@@ -670,13 +685,13 @@ def monkeypatch_man_writer() -> None:
setattr(ManualPageTranslator, 'header', header)
def visit_image(self: ManualPageTranslator, node: figure) -> None:
def visit_image(self: ManualPageTranslator, node: Element) -> None:
pass
def depart_image(self: ManualPageTranslator, node: figure) -> None:
def depart_image(self: ManualPageTranslator, node: Element) -> None:
pass
def depart_figure(self: ManualPageTranslator, node: figure) -> None:
def depart_figure(self: ManualPageTranslator, node: Element) -> None:
self.body.append(' (images not supported)\n')
Translator.depart_figure(self, node)
@@ -684,8 +699,8 @@ def monkeypatch_man_writer() -> None:
setattr(ManualPageTranslator, 'depart_image', depart_image)
setattr(ManualPageTranslator, 'depart_figure', depart_figure)
orig_astext = getattr(ManualPageTranslator, 'astext')
def astext(self: ManualPageTranslator) -> Any:
orig_astext = Translator.astext
def astext(self: Translator) -> Any:
b = []
for line in self.body:
if line.startswith('.SH'):
@@ -694,9 +709,9 @@ def monkeypatch_man_writer() -> None:
parts[0] = parts[0].capitalize()
line = x + ' ' + '\n'.join(parts)
b.append(line)
setattr(self, 'body', b)
self.body = b
return orig_astext(self)
setattr(ManualPageTranslator, 'astext', astext)
setattr(Translator, 'astext', astext)
def setup_man_pages() -> None:
@@ -753,6 +768,7 @@ def setup(app: Any) -> None:
write_remote_control_protocol_docs()
write_color_names_table()
write_conf_docs(app, kn)
app.add_config_value('string_replacements', {}, True)
app.connect('source-read', replace_string)
app.add_config_value('analytics_id', '', 'env')
app.connect('html-page-context', add_html_context)

View File

@@ -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``.
Also, you can use :code:`globinclude` to include files
matching a shell glob pattern and :code:`envinclude` to include configuration
from environment variables. Finally, you can dynamically generate configuration
by running a program using :code:`geninclude`. For example::
from environment variables. For example::
# Include other.conf
include other.conf
# Include *.conf files from all subdirs of kitty.d inside the kitty config dir
globinclude kitty.d/**/*.conf
# Include the *contents* of all env vars starting with 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

View File

@@ -1,4 +1,4 @@
.. _notifications_on_the_desktop:
.. _desktop_notifications:
Desktop notifications
@@ -25,7 +25,7 @@ the set :code:`a-zA-Z0-9-_/\+.,(){}[]*&^%$#@!`~`. The payload must be
interpreted based on the metadata section. The two semi-colons *must* always be
present even when no metadata is present.
Before going into details, let's see how one can display a simple, single line
Before going into details, lets see how one can display a simple, single line
notification from a shell script::
printf '\x1b]99;;Hello world\x1b\\'
@@ -395,17 +395,17 @@ Key Value
``c`` ``c=1`` if the terminal supports close events, otherwise the ``c``
must be omitted.
``o`` Comma separated list of occasions from the ``o`` key that the
``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`` Comma spearated 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 at least ``system`` and ``silent``.
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
@@ -450,10 +450,10 @@ Key Value Default Description
encoded UTF-8
application name
``g`` :ref:`identifier` ``unset`` Identifier for icon data. Make these globally unique,
``g`` :ref:`identifier` ``unset`` Identifier for icon data. Make these globally unqiue,
like an UUID.
``i`` :ref:`identifier` ``unset`` Identifier for the notification. Make these globally unique,
``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.
@@ -527,7 +527,7 @@ 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
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.

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
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?
-----------------------------------------------------------------------
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.
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
@@ -206,25 +190,29 @@ remote control command, for details, see :ref:`at-set-colors`.
To change colors when SSHing into a remote host, use the :opt:`color_scheme
<kitten-ssh.color_scheme>` setting for the :doc:`ssh kitten <kittens/ssh>`.
Additionally, you can use the escape code described in :doc:`color-stack`
to set colors in a single window.
Examples of using OSC escape codes to set colors::
Additionally, You can use the
`OSC terminal escape codes <https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands>`__
to set colors. Examples of using OSC escape codes to set colors::
Change the default foreground color:
printf '\x1b]21;foreground=#ff0000\x1b\\'
printf '\x1b]10;#ff0000\x1b\\'
Change the default background color:
printf '\x1b]21;background=blue\x1b\\'
printf '\x1b]11;blue\x1b\\'
Change the cursor color:
printf '\x1b]21;cursor=blue\x1b\\'
printf '\x1b]12;blue\x1b\\'
Change the selection background color:
printf '\x1b]21;selection_background=blue\x1b\\'
printf '\x1b]17;blue\x1b\\'
Change the selection foreground color:
printf '\x1b]21;selection_foreground=blue\x1b\\'
printf '\x1b]19;blue\x1b\\'
Change the nth color (0 - 255):
printf '\x1b]21;n=green\x1b\\'
printf '\x1b]4;n;green\x1b\\'
See :doc:`color-stack` for details on the syntax for specifying colors and
how to query current colors.
You can use various syntaxes/names for color specifications in the above
examples. See `XParseColor <https://linux.die.net/man/3/xparsecolor>`__
for full details.
If a ``?`` is given rather than a color specification, kitty will respond
with the current value for the specified color.
How do I specify command line options for kitty on macOS?
@@ -235,10 +223,6 @@ workaround that limitation, |kitty| will read command line options from the file
:file:`<kitty config dir>/macos-launch-services-cmdline` when it is launched
from the GUI, i.e. by clicking the |kitty| application icon or using
``open -a kitty``. Note that this file is *only read* when running via the GUI.
The contents of the file are assumed to be the command line to pass to kitty in
shell syntax, for example::
--single-instance --override background=red
You can, of course, also run |kitty| from a terminal with command line options,
using: :file:`/Applications/kitty.app/Contents/MacOS/kitty`.
@@ -279,9 +263,9 @@ fonts to be freely resizable, so it does not support bitmapped fonts.
symbols, so to force kitty to use the pure NERD font for NERD symbols,
add the following line to :file:`kitty.conf`::
# Nerd Fonts v3.3.0
# Nerd Fonts v3.2.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+e0d7,U+e200-U+e2a9,U+e300-U+e3e3,U+e5fa-U+e6b1,U+e700-U+e7c5,U+ed00-U+efc1,U+f000-U+f2ff,U+f000-U+f2e0,U+f300-U+f372,U+f400-U+f533,U+f0001-U+f1af0 Symbols Nerd Font Mono
Those Unicode symbols not in the `Unicode private use areas
<https://en.wikipedia.org/wiki/Private_Use_Areas>`__ are
@@ -325,9 +309,10 @@ Then, the font will be available in ``kitten choose-fonts``.
How can I assign a single global shortcut to bring up the kitty terminal?
-----------------------------------------------------------------------------
Use the :ref:`panel kitten <quake>`, this allows you to use kitty as a quick
access Quake like terminal and even to use kitty as the desktop background, if
so desired.
Bringing up applications on a single key press is the job of the window
manager/desktop environment. For ways to do it with kitty (or indeed any
terminal) in different environments,
see :iss:`here <45>`.
I do not like the kitty icon!
@@ -377,23 +362,9 @@ many alternate icons available, click on an icon to visit its homepage:
: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
.. image:: https://github.com/mtklr/kitty-nyan-icon/raw/main/kitty-nyan.svg
:target: https://github.com/mtklr/kitty-nyan-icon
:width: 256
You can put :file:`kitty.app.icns` (macOS only) or :file:`kitty.app.png` in the
On macOS and X11 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
automatically at startup. On X11 and Wayland, 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.
automatically at startup. On X11, this will set the icon for kitty windows.
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
@@ -416,20 +387,13 @@ also set it with the following command:
You can also change the icon manually by following the steps:
.. tab:: macOS
#. Find :file:`kitty.app` in the Applications folder, select it and press :kbd:`⌘+I`
#. Drag :file:`kitty.icns` onto the application icon in the kitty info pane
#. Delete the icon cache and restart Dock:
#. Find :file:`kitty.app` in the Applications folder, select it and press :kbd:`⌘+I`
#. Drag :file:`kitty.icns` onto the application icon in the kitty info pane
#. Delete the icon cache and restart Dock::
.. code-block:: sh
rm /var/folders/*/*/*/com.apple.dock.iconcache; killall Dock
.. tab:: Linux
#. Copy :file:`kitty.desktop` from the installation location (usually
:file:`/usr/share/applications` to :file:`~/.local/share/applications`
#. Edit the copied desktop file changing the ``Icon`` line to have
the absolute path to your desired icon.
rm /var/folders/*/*/*/com.apple.dock.iconcache; killall Dock
How do I map key presses in kitty to different keys in the terminal program?
@@ -477,18 +441,7 @@ You need to make sure that the environment variables you define in your shell's
rc files are either also defined system wide or via the :opt:`env` directive in
:file:`kitty.conf`. Common environment variables that cause issues are those
related to localization, such as :envvar:`LANG`, ``LC_*`` and loading of
configuration files such as ``XDG_*``, :envvar:`KITTY_CONFIG_DIRECTORY` and,
most importantly, ``PATH`` to locate binaries.
The simplest way to fix this is to have kitty load the environment variables
from your shell configuration at startup using the :opt:`env` directive,
adding the following to :file:`kitty.conf`::
env read_from_shell=PATH LANG LC_* XDG_* EDITOR VISUAL
This works for POSIX compliant shells and the fish shell. Note that it
does add significantly to kitty startup time, so use only if really necessary.
This feature was added in version ``0.43.2``.
configuration files such as ``XDG_*``, :envvar:`KITTY_CONFIG_DIRECTORY`.
To see the environment variables that kitty sees, you can add the following
mapping to :file:`kitty.conf`::
@@ -502,8 +455,8 @@ setup environment variables system-wide, so people end up putting them in all
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>`,
do not use them, if at all possible. kitty contains features that do all of what
@@ -525,10 +478,9 @@ for tmux refusing to support images.
If you use any of the advanced features that kitty has innovated, such as
:doc:`styled underlines </underlines>`, :doc:`desktop notifications
</desktop-notifications>`, :doc:`variable sized text </text-sizing-protocol>`,
:doc:`extended keyboard support </keyboard-protocol>`,
:doc:`file transfer </kittens/transfer>`, :doc:`the ssh kitten </kittens/ssh>`,
:doc:`shell integration </shell-integration>` etc. they may or may not work,
</desktop-notifications>`, :doc:`extended keyboard support
</keyboard-protocol>`, :doc:`file transfer </kittens/transfer>`, :doc:`the ssh
kitten </kittens/ssh>`, :doc:`shell integration </shell-integration>` etc. they may or may not work,
depending on the whims of tmux's maintainer, your version of tmux, etc.

View File

@@ -127,7 +127,7 @@ receiving::
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``
response is received from the terminal. The terminal waits 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::
← action=status id=someid status=OK

View File

@@ -224,14 +224,6 @@ Variables that kitty sets when running child programs
Set when enabling :ref:`shell_integration`. It is automatically removed by
the shell integration scripts.
.. envvar:: KITTY_SI_RUN_COMMAND_AT_STARTUP
Set this to an expression that the kitty shell integration scripts will
``eval`` after the shell is started. Note that this environment variable
is ignored when present in the environment in which kitty itself is launched
in. It is most useful with the ``--env`` flag for the :doc:`launch <launch>`
action.
.. envvar:: ZDOTDIR
Set when enabling :ref:`shell_integration` with :program:`zsh`, allowing

View File

@@ -28,7 +28,6 @@ alpha-blending and text over graphics.
Some applications that use the kitty graphics protocol:
* `awrit <https://github.com/chase/awrit>`_ - Chromium-based web browser rendered in Kitty with mouse and keyboard support
* `blackcat <https://github.com/j-c-m/blackcat>`_ - a modern compatible cat with image 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
@@ -41,7 +40,6 @@ Some applications that use the kitty graphics protocol:
* `timg <https://github.com/hzeller/timg>`_ - a terminal image and video viewer
* `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
* `vat <https://github.com/jzbrooks/vat>`_ - a terminal image viewer for vector graphics, including Android Vector Drawables
* `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
@@ -59,13 +57,9 @@ Libraries:
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>`_
* `iTerm2 <https://github.com/gnachman/iTerm2/commit/4fe5b2173193b6c3e45234b6b2ab7a144a5cfa01>`_
Getting the window size
@@ -102,7 +96,7 @@ code to demonstrate its use
buf = array.array('H', [0, 0, 0, 0])
fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ, buf)
print((
'number of rows: {} number of columns: {} '
'number of rows: {} number of columns: {}'
'screen width: {} screen height: {}').format(*buf))
.. tab:: Go
@@ -133,30 +127,19 @@ code to demonstrate its use
}
.. tab:: POSIX sh
.. tab:: Bash
.. code-block:: sh
#!/bin/sh
#!/bin/bash
read rows cols <<EOF
$(command stty size)
EOF
# This uses the kitten standalone binary from kitty to get the pixel sizes
# since we can't do IOCTLs directly. Fortunately, kitten is a static exe
# pre-built for every Unix like OS under the sun.
oldstty=$(command stty -g)
command stty raw -echo
printf "\033[14t"
response=""
while : ; do
char=$(command dd bs=1 count=1 2>/dev/null)
[ "$char" = "t" ] && break
response="${response}${char}"
done
command stty "$oldstty"
h=$(echo "$response" | cut -d';' -f2)
w=$(echo "$response" | cut -d';' -f3)
printf "number of rows: %d number of columns: %d" "$rows" "$cols"
printf " screen width: %d screen height: %d\n" "$w" "$h"
builtin read -r rows cols < <(command stty size)
IFS=x builtin read -r width height < <(command kitten icat --print-window-size); builtin unset IFS
builtin echo "number of rows: $rows number of columns: $cols screen width: $width screen height: $height"
Note that some terminals return ``0`` for the width and height values. Such
@@ -167,9 +150,7 @@ You can also use the *CSI t* escape code to get the screen size. Send
``<ESC>[14t`` to ``STDOUT`` and kitty will reply on ``STDIN`` with
``<ESC>[4;<height>;<width>t`` where ``height`` and ``width`` are the window
size in pixels. This escape code is supported in many terminals, not just
kitty. A more precise version of this escape code, which is however supported
in less terminals is ``<ESC>[16t`` which causes the terminal to reply with the
pixel dimensions of a single cell.
kitty.
A minimal example
------------------
@@ -177,55 +158,61 @@ A minimal example
Some minimal code to display PNG images in kitty, using the most basic
features of the graphics protocol:
.. tab:: POSIX sh
.. tab:: Bash
.. code-block:: sh
#!/bin/sh
send_chunked() {
first="y"
while IFS= read -r chunk; do
metadata=""; [ "$first" = "y" ] && { metadata="a=T,f=100,"; first="n"; }
printf "\033_G%sm=1;%s\033\\" "${metadata}" "${chunk}"
done
[ "$first" = "n" ] && { printf "\033_Gm=0;\033\\"; return 0; }
return 1
}
#!/bin/bash
transmit_png() {
# Different systems have different or missing base64 executables.
# The sed command below adds a trailing newline which openssl
# base64 does not produce and is needed for reading via read -r
{ command base64 -w 4096 "$1" 2>/dev/null | send_chunked; } || \
{ command base64 -b 4096 "$1" 2>/dev/null | send_chunked; } || \
{ command openssl base64 -e -A -in "$1" | command sed '$a\' | command fold -b -w 4096 | send_chunked; }
data=$(base64 "$1")
data="${data//[[:space:]]}"
builtin local pos=0
builtin local chunk_size=4096
while [ $pos -lt ${#data} ]; do
builtin printf "\e_G"
[ $pos = "0" ] && printf "a=T,f=100,"
builtin local chunk="${data:$pos:$chunk_size}"
pos=$(($pos+$chunk_size))
[ $pos -lt ${#data} ] && builtin printf "m=1"
[ ${#chunk} -gt 0 ] && builtin printf ";%s" "${chunk}"
builtin printf "\e\\"
done
}
transmit_png "$1"
.. tab:: Python
.. code-block:: python
#!/usr/bin/env python
#!/usr/bin/python
import sys
from base64 import standard_b64encode
first, eof, buf = True, False, memoryview(bytearray(3 * 4096 // 4))
w = sys.stdout.buffer.write
def serialize_gr_command(**cmd):
payload = cmd.pop('payload', None)
cmd = ','.join(f'{k}={v}' for k, v in cmd.items())
ans = []
w = ans.append
w(b'\033_G'), w(cmd.encode('ascii'))
if payload:
w(b';')
w(payload)
w(b'\033\\')
return b''.join(ans)
def write_chunked(**cmd):
data = standard_b64encode(cmd.pop('data'))
while data:
chunk, data = data[:4096], data[4096:]
m = 1 if data else 0
sys.stdout.buffer.write(serialize_gr_command(payload=chunk, m=m,
**cmd))
sys.stdout.flush()
cmd.clear()
with open(sys.argv[-1], 'rb') as f:
while not eof:
p = buf[:]
while p and not eof:
n = f.readinto1(p)
p, eof = p[n:], n == 0
encoded = standard_b64encode(buf[:len(buf)-len(p)])
metadata, first = "a=T,f=100," if first else "", False
w(f'\x1b_G{metadata}m={0 if eof else 1};'.encode('ascii'))
w(encoded)
w(b'\x1b\\')
write_chunked(a='T', f=100, data=f.read())
Save this script as :file:`send-png`, then you can use it to display any PNG
@@ -346,7 +333,7 @@ similar to reporting any other kind of I/O error. Since the file paths come
from potentially untrusted sources, terminal emulators **must** refuse to read
any device/socket/etc. special files. Only regular files are allowed.
Additionally, terminal emulators may refuse to read files in *sensitive*
parts of the filesystem, such as :file:`/proc`, :file:`/sys`, :file:`/dev`, etc.
parts of the filesystem, such as :file:`/proc`, :file:`/sys`, :file:`/dev/`, etc.
Local client
^^^^^^^^^^^^^^
@@ -364,7 +351,7 @@ Here we tell the terminal emulator to read compressed image data from
the specified shared memory object.
The client can also specify a size and offset to tell the terminal emulator
to only read a part of the specified file. This is done using the ``S`` and ``O``
to only read a part of the specified file. The is done using the ``S`` and ``O``
keys respectively. For example::
<ESC>_Gs=10,v=2,t=s,S=80,O=10;<encoded /some-shared-memory-name><ESC>\
@@ -428,7 +415,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
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
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
@@ -476,11 +465,10 @@ id. To do so add the ``p`` key with a number between ``1`` and ``4294967295``.
When you specify a placement id, it will be added to the acknowledgement code
above. Every placement is uniquely identified by the pair of the ``image id``
and the ``placement id``. If you specify a placement id for an image that does
not have an id (i.e. has id=0), it will be ignored, i.e. the placement will not
get an id. In particular this means there can exist multiple images with
``image id=0, placement id=0``. Not specifying a placement id or using ``p=0``
for multiple put commands (``a=p``) with the same non-zero image id results in
multiple placements the image.
not have an id (i.e. has id=0), it will be ignored. In particular this means
there can exist multiple images with ``image id=0, placement id=0``. Not
specifying a placement id or using ``p=0`` for multiple put commands (``a=p``)
with the same non-zero image id results in multiple placements the image.
An example response::
@@ -491,14 +479,6 @@ second one will replace the first. This can be used to resize or move
placements around the screen, without flicker.
.. note::
When re-transmitting image data for a specific id, the existing image and
all its placements must be deleted. The new data replaces the old image data
but is not actually displayed until a placement for it is created. This is
to avoid divergent behavior in the case when unrelated programs happen to re-use
image ids in the same session.
.. versionadded:: 0.19.3
Support for specifying placement ids (see :doc:`kittens/query_terminal` to query kitty version)
@@ -774,9 +754,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
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::
<ESC>_Ga=d<ESC>\ # delete all visible placements
@@ -904,12 +881,12 @@ on.
Finally, while transferring frame data, the frame *gap* can also be specified
using the ``z`` key. The gap is the number of milliseconds to wait before
displaying the next frame when the animation is running. A value of ``z=0`` is
ignored (acts as though ``z`` was unspecified), ``z=positive number`` sets the
gap to the specified number of milliseconds and ``z=negative number`` creates a
*gapless* frame. Gapless frames are not displayed to the user since they are
instantly skipped over, however they can be useful as the base data for
subsequent frames. For example, for an animation where the background remains
the same and a small object or two move.
ignored, ``z=positive number`` sets the gap to the specified number of
milliseconds and ``z=negative number`` creates a *gapless* frame. Gapless
frames are not displayed to the user since they are instantly skipped over,
however they can be useful as the base data for subsequent frames. For example,
for an animation where the background remains the same and a small object or two
move.
Controlling animations
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -939,9 +916,9 @@ animation. ``s=2`` runs the animation, but in *loading* mode, in this mode when
reaching the last frame, instead of looping, the terminal will wait for the
arrival of more frames. ``s=3`` runs the animation normally, after the last
frame, the terminal loops back to the first frame. The number of loops can be
controlled by the ``v`` key. ``v=0`` is ignored (acts as though ``v`` was not
specified), ``v=1`` is loop infinitely, and any other positive number is loop
``number - 1`` times. Note that stopping the animation resets the loop counter.
controlled by the ``v`` key. ``v=0`` is ignored, ``v=1`` is loop infinitely,
and any other positive number is loop ``number - 1`` times. Note that stopping
the animation resets the loop counter.
Finally, the *gap* for frames can be set using the ``z`` key. This can be
specified either when the frame is created as part of the transmit escape code
@@ -1021,8 +998,8 @@ take, and the default value they take when missing. All integers are 32-bit.
Key Value Default Description
======= ==================== ========= =================
``a`` Single character. ``t`` The overall action this graphics command is performing.
``(a, c, d, f, ``t`` - transmit data, ``T`` - transmit data and display image,
p, q, t, T)`` ``q`` - query terminal, ``p`` - put (display) previous transmitted image,
``(a, c, d, f, `` ``t`` - transmit data, ``T`` - transmit data and display image,
``p, q, t, T)`` ``q`` - query terminal, ``p`` - put (display) previous transmitted image,
``d`` - delete image, ``f`` - transmit data for animation frames,
``a`` - control animation, ``c`` - compose animation frames

View File

@@ -1,9 +1,7 @@
kitty
==========================================================
*If you live in the terminal, kitty is made for YOU!*
The fast, feature-rich, GPU based terminal emulator.
*The fast, feature-rich, GPU based terminal emulator*
.. toctree::
:hidden:
@@ -12,7 +10,6 @@ The fast, feature-rich, GPU based terminal emulator.
overview
faq
support
sessions
performance
changelog
integrations
@@ -22,7 +19,7 @@ The fast, feature-rich, GPU based terminal emulator.
.. tab:: Fast
* Uses GPU and SIMD vector CPU instructions for :doc:`best in class performance <performance>`
* Uses GPU and SIMD vector CPU instructions for :doc:`best in class <performance>`
* Uses threaded rendering for :iss:`absolutely minimal latency <2701#issuecomment-636497270>`
* Performance tradeoffs can be :ref:`tuned <conf-kitty-performance>`

View File

@@ -26,42 +26,47 @@ A terminal PDF/DJVU/CBR viewer
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A terminal PDF viewer
.. _tool_fancy_cat:
.. _tool_mdcat:
`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_mcat:
`mcat <https://github.com/Skardyy/mcat>`_
`mdcat <https://github.com/lunaryorn/mdcat>`_
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Display various types of files nicely formatted with images in the terminal
Display markdown files nicely formatted with images in the terminal
`dawn <https://github.com/andrewmd5/dawn>`__
.. _tool_ranger:
`ranger <https://github.com/ranger/ranger>`_
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A terminal file manager, with previews of file contents powered by kitty's
graphics protocol.
.. _tool_nnn:
`nnn <https://github.com/jarun/nnn/>`_
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Another terminal file manager, with previews of file contents powered by kitty's
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_hunter:
`hunter <https://github.com/rabite0/hunter>`_
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Another terminal file manager, with previews of file contents powered by kitty's
graphics protocol.
A markdown editor that uses the text-sizing protocol for large headings and
the graphics protocol for images.
.. _tool_presentterm:
`presenterm <https://github.com/mfontanini/presenterm>`_
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Show markdown based slides with images in your terminal, powered by the
kitty graphics protocol.
.. _tool_mdfried:
`mdfried <https://github.com/benjajaja/mdfried>`_
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Markdown viewer that can render big headers with the text-sizing-protocol, and
also render images with the kitty graphics protocol.
.. _tool_term_image:
`term-image <https://github.com/AnonymouX47/term-image>`__
@@ -95,30 +100,17 @@ base application that uses kitty's graphics protocol for images.
A text mode WWW browser that supports kitty's graphics protocol to display
images.
.. _tool_awrit:
`awrit <https://github.com/chase/awrit>`__
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A full Chromium based web browser running in the terminal using kitty's
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:
`mpv <https://github.com/mpv-player/mpv/commit/874e28f4a41a916bb567a882063dd2589e9234e1>`_
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A video player that can play videos in the terminal.
.. code-block:: sh
mpv --profile=sw-fast --vo=kitty --vo-kitty-use-shm=yes --really-quiet video.mkv
.. _tool_timg:
`timg <https://github.com/hzeller/timg>`_
@@ -128,46 +120,6 @@ plays videos. Fast multi-threaded loading, JPEG exif rotation, grid view and
connecting to the webcam make it a versatile terminal utility.
File managers
-------------------
.. _tool_ranger:
`ranger <https://github.com/ranger/ranger>`_
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A terminal file manager, with previews of file contents powered by kitty's
graphics protocol.
.. _tool_nnn:
`nnn <https://github.com/jarun/nnn/>`_
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Another terminal file manager, with previews of file contents powered by kitty's
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:
`hunter <https://github.com/rabite0/hunter>`_
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Another terminal file manager, with previews of file contents powered by kitty's
graphics protocol.
.. _tool_presentterm:
System and data visualisation tools
---------------------------------------
@@ -180,13 +132,9 @@ protocol
.. _tool_matplotlib:
matplotlib
^^^^^^^^^^^^^^
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>`__
`matplotlib <https://github.com/jktr/matplotlib-backend-kitty>`_
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Show matplotlib plots directly in kitty
.. _tool_KittyTerminalImage:
@@ -206,15 +154,28 @@ by kitty's graphics protocol for displaying plots
`gnuplot <http://www.gnuplot.info/>`_
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A graphing and data visualization tool that has support for the kitty graphics
protocol, with its ``kittygd`` and ``kittycairo`` backends.
A graphing and data visualization tool that can be made to display its output in
kitty with the following bash snippet:
.. _tool_k-nine:
.. code-block:: sh
`k-nine <https://github.com/talwrii/kitty-plotnine>`_
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
function iplot {
cat <<EOF | gnuplot
set terminal pngcairo enhanced font 'Fira Sans,10'
set autoscale
set samples 1000
set output '|kitten icat --stdin yes'
set object 1 rectangle from screen 0,0 to screen 1,1 fillcolor rgb"#fdf6e3" behind
plot $@
set output '/dev/null'
EOF
}
A wrapper around the :code:`plotnine` library which lets you plot data from the command-line with bash one-liners.
Add this to bashrc and then to plot a function, simply do:
.. code-block:: sh
iplot 'sin(x*3)*exp(x*.2)'
.. tool_tgutui:
@@ -247,13 +208,6 @@ A tool to display weather information in your terminal with curl
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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
-----------------------
@@ -290,7 +244,7 @@ Allows easily running tests in a terminal window
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
@@ -317,50 +271,10 @@ Live incremental search of the 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
Password managers
---------------------
`1password <https://github.com/mm-zacharydavison/kitty-kitten-1password>`__
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Allow injecting passwords from 1Password into kitty.
`BitWarden <https://github.com/dnanhkhoa/kitty-password-manager>`__
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Inject passwords from BitWarden into kitty
Miscellaneous
------------------
.. tool_doom:
DOOM
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Play the classic shooter DOOM in `kitty <https://github.com/cryptocode/terminal-doom>`__ or even inside `neovim inside kitty
<https://github.com/seandewar/actually-doom.nvim>`__.
.. 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:
`kitty-smart-tab <https://github.com/yurikhan/kitty-smart-tab>`_

View File

@@ -34,21 +34,16 @@ inside the kitty terminal to report key events.
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 `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 `TuiOS terminal (multiplexer) <https://github.com/Gaurav-Gosain/tuios/issues/26>`__
* The `alacritty terminal <https://github.com/alacritty/alacritty/pull/7125>`__
* The `rio terminal <https://github.com/raphamorim/rio/commit/cd463ca37677a0fc48daa8795ea46dadc92b1e95>`__
Libraries implementing this protocol:
* The `notcurses library <https://github.com/dankamongmen/notcurses/issues/2131>`__
* The `crossterm library <https://github.com/crossterm-rs/crossterm/pull/688>`__
* The `textual library <https://github.com/Textualize/textual/pull/4631>`__
* The vaxis library `go <https://sr.ht/~rockorager/vaxis/>`__ and `zig <https://github.com/rockorager/libvaxis/>`__
* The `bubbletea library <https://github.com/charmbracelet/bubbletea/issues/869>`__
Programs implementing this protocol:
@@ -58,12 +53,10 @@ Programs implementing this protocol:
* The `kakoune text editor <https://github.com/mawww/kakoune/issues/4103>`__
* The `dte text editor <https://gitlab.com/craigbarnes/dte/-/issues/138>`__
* The `Helix text editor <https://github.com/helix-editor/helix/pull/4939>`__
* The `Flow control editor <https://github.com/neurocyte/flow?tab=readme-ov-file#requirements>`__
* The `far2l file manager <https://github.com/elfmz/far2l/commit/e1f2ee0ef2b8332e5fa3ad7f2e4afefe7c96fc3b>`__
* The `Yazi file manager <https://github.com/sxyazi/yazi>`__
* 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:
@@ -152,14 +145,14 @@ user presses, for example, :kbd:`ctrl+shift+a` the escape code would be ``CSI
97;modifiers u``. It *must not* be ``CSI 65; modifiers u``.
If *alternate key reporting* is requested by the program running in the
terminal, the terminal can send two additional Unicode codepoints, the *shifted
key* and *base layout key*, separated by colons. The shifted key is simply the
upper-case version of ``unicode-codepoint``, or more technically, the shifted
version, in the currently active keyboard layout. So `a` becomes `A` and so on,
based on the current keyboard layout. This is needed to be able to match
against a shortcut such as :kbd:`ctrl+plus` which depending on the type of
keyboard could be either :kbd:`ctrl+shift+equal` or :kbd:`ctrl+plus`. Note that
the shifted key must be present only if shift is also present in the modifiers.
terminal, the terminal can send two additional Unicode codepoints, the
*shifted key* and *base layout key*, separated by colons.
The shifted key is simply the upper-case version of ``unicode-codepoint``, or
more technically, the shifted version. So `a` becomes `A` and so on, based on
the current keyboard layout. This is needed to be able to match against a
shortcut such as :kbd:`ctrl+plus` which depending on the type of keyboard could
be either :kbd:`ctrl+shift+equal` or :kbd:`ctrl+plus`. Note that the shifted
key must be present only if shift is also present in the modifiers.
The *base layout key* is the key corresponding to the physical key in the
standard PC-101 key layout. So for example, if the user is using a Cyrillic
@@ -167,7 +160,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
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
sub-field for the shifted key, like this::
@@ -244,19 +237,13 @@ The terminal can optionally send the text associated with key events as a
sequence of Unicode code points. This behavior is opt-in by the :ref:`progressive
enhancement <progressive_enhancement>` mechanism described below. Some examples::
shift+a -> CSI 97 ; 2 ; 65 u # The text 'A' is reported as 65
alt+a -> CSI 0 ; ; 229 u # The text 'å' is reported as 229
shift+a -> CSI 97 ; 2 ; 65 u # The text 'A' is reported as 65
option+a -> CSI 97 ; ; 229 u # The text 'å' is reported as 229
If multiple code points are present, they must be separated by colons. If no
known key is associated with the text the key number ``0`` must be used. The
associated text must not contain control codes (control codes are code points
below U+0020 and codepoints in the C0 and C1 blocks). In the above example, the
:kbd:`alt` modifier is consumed by the OS itself to produce the text å and not
sent to the terminal emulator, which gets only a "text input" event and no
information about modifiers, thus the event gets encoded with no modifiers.
The exact behavior in these situations depends on the OS, keyboard layout, IME
system in use and so on. In general, if the terminal emulator receives no key
information, the key number 0 must be used to indicate a pure "text event".
below U+0020 and codepoints in the C0 and C1 blocks).
Non-Unicode keys
@@ -346,7 +333,7 @@ the bytes used for CSI control codes.
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
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>`.
With this flag turned on, all key events that do not generate text are
@@ -363,7 +350,7 @@ are the :kbd:`Enter`, :kbd:`Tab` and :kbd:`Backspace` keys which still generate
bytes as in legacy mode this is to allow the user to type and execute commands
in the shell such as ``reset`` after a program that sets this mode crashes
without clearing it. Note that the Lock modifiers are not reported for text
producing keys, to keep them usable in legacy programs. To get lock modifiers
producing keys, to keep them useable in legacy programs. To get lock modifiers
for all keys use the :ref:`report_all_keys` enhancement.
.. _report_events:
@@ -389,13 +376,8 @@ Report alternate keys
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This progressive enhancement (``0b100``) causes the terminal to report
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
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.
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.
.. _report_all_keys:
@@ -414,20 +396,17 @@ Report associated text enhancement below.
Additionally, with this mode, events for pressing modifier keys are reported.
Note that *all* keys are reported as escape codes, including :kbd:`Enter`,
:kbd:`Tab`, :kbd:`Backspace` etc. Note that this enhancement implies all keys
are automatically disambiguated as well, since they are represented in their
canonical escape code form.
:kbd:`Tab`, :kbd:`Backspace` etc.
.. _report_text:
Report associated text
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This progressive enhancement (``0b10000``) *additionally* causes key events that
generate text to be reported as ``CSI u`` escape codes with the text embedded
in the escape code. See :ref:`text_as_codepoints` above for details on the
mechanism. Note that this flag is an enhancement to :ref:`report_all_keys`
and is undefined if used without it.
This progressive enhancement (``0b10000``) causes key events that generate text
to be reported as ``CSI u`` escape codes with the text embedded in the escape
code. See :ref:`text_as_codepoints` above for details on the mechanism.
.. _detection:
@@ -442,13 +421,6 @@ followed by request for the `primary device attributes
attributes is received without getting back an answer for the progressive
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
--------------------------------
@@ -473,10 +445,7 @@ These keys are encoded using three schemes::
In the above, if there are no modifiers, the modifier parameter is omitted.
The modifier value is encoded as described in the :ref:`modifiers` section,
above, except that lock keys (such as :kbd:`Num lock` and :kbd:`Caps lock`)
are not encoded as the legacy mode has no encoding for them.
When the second form is used, the number is always ``1`` and must be
above. When the second form is used, the number is always ``1`` and must be
omitted if the modifiers field is also absent. The third form becomes the
second form when modifiers are present (``SS3 is the bytes 0x1b 0x4f``).
@@ -567,8 +536,8 @@ Any other combination of modifiers with these keys is output as the appropriate
.. csv-table:: Example encodings
:header: "Key", "Plain", "shift", "alt", "ctrl", "shift+alt", "alt+ctrl", "ctrl+shift"
"i", "i (105)", "I (73)", "ESC i", "\t (9)", "ESC I", "ESC \t", "CSI 105; 6 u"
"3", "3 (51)", "# (35)", "ESC 3", "ESC (27)", "ESC #", "ESC ESC", "CSI 51; 6 u"
"i", "i (105)", "I (73)", "ESC i", ") (41)", "ESC I", "ESC )", "CSI 105; 6 u"
"3", "3 (51)", "# (35)", "ESC 3", "3 (51)", "ESC #", "ESC 3", "CSI 51; 6 u"
";", "; (59)", ": (58)", "ESC ;", "; (59)", "ESC :", "ESC ;", "CSI 59; 6 u"
.. note::

View File

@@ -1,129 +0,0 @@
Selecting files, fast
========================
.. only:: man
Overview
--------------
.. versionadded:: 0.45.0
.. only:: not man
.. figure:: /screenshots/choose-files.webp
:alt: The choose files kitten, showing metadata and title from an e-book file
:align: center
:width: 100%
The choose-files kitten is designed to allow you to select files, very fast,
with just a few key strokes. It operates like `fzf
<https://github.com/junegunn/fzf/>`__ and similar fuzzy finders, except that
it is specialised for finding files. As such it supports features such as
filtering by file type, file type icons, content previews and
so on, out of the box. It can be used as a drop in (but much more efficient and
keyboard friendly) replacement for the :guilabel:`File open and save`
dialog boxes common to GUI programs. On Linux, with the help of the
:doc:`desktop-ui </kittens/desktop-ui>` kitten, you can even convince
most GUI programs on your computer to use this kitten instead of regular file
dialogs.
Simply run it as::
kitten choose-files
to select a single file from the tree rooted at the current working directory.
Type a few letters from the filename and once it becomes the top selection,
press :kbd:`Enter`. You can change the current directory by selecting a
directory and pressing the :kbd:`Tab` key. :kbd:`Shift+Tab` goes up one
directory level.
If you want to choose a file and insert it into your shell prompt at the
current cursor position, press :sc:`insert_chosen_file` for files or
:sc:`insert_chosen_directory` for directories. Similarly, to have a file
chosen in a command line, use, for example::
some-command $(kitten choose-file)
Note, that the above may not work in a complicated pipeline as it performs
terminal I/O and needs exclusive access to the tty device while choosing a
file.
Creating shortcuts to favorite/frequently used directories
------------------------------------------------------------
You can create keyboard shortcuts to quickly switch to any directory in
:file:`choose-files.conf`. For example:
.. code-block:: conf
map ctrl+t cd /tmp
map alt+p cd ~/my/project
Selecting multiple files
-----------------------------
When you wish to select multiple files, start the kitten with :option:`--mode
<kitty +kitten choose_files --mode>`:code:`=files`. Then instead of pressing
:kbd:`Enter`, press :kbd:`Shift+Enter` instead and the file will be added to the list
of selections. You can also hold the :kbd:`Ctrl` key and click on files to add
them to the selections. Similarly, you can hold the :kbd:`Alt` key and click to
select ranges of files (similar to using :kbd:`Shift+click` in a GUI app).
Press :kbd:`Enter` on the last selected file to finish. The list of selected
files is displayed at the bottom of the kitten and you can click on them
to deselect a file. Similarly, pressing :kbd:`Shift+Enter` will un-select a
previously selected file.
Hidden and ignored files
--------------------------
By default, the kitten does not process hidden files and directories (whose
names start with a period). This can be :opt:`changed in the configuration <kitten-choose_files.show_hidden>`
and also at runtime via the clickable link to the right of the search input.
Similarly, the kitten respects both :file:`.gitignore` and :file:`.ignore`
files, by default. This can also be changed both :opt:`in configuration
<kitten-choose_files.respect_ignores>` or at runtime. Note that
:file:`.gitignore` files are only respected if there is also a :file:`.git`
directory present. The kitten also supports the global :file:`.gitignore` file,
though it applies only inside git working trees. You can specify :opt:`global ignore
patterns <kitten-choose_files.ignore>`, that apply everywhere in :file:`choose-files.conf`.
Selecting non-existent files (save file names)
-------------------------------------------------
This kitten can also be used to select non-existent files, that is a new file
for a :guilabel:`Save file` type of dialog using :option:`--mode <kitty +kitten
choose_files --mode>`:code:`=save-file`. Once you have changed to the directory
you want the file to be in (using the :kbd:`Tab` key),
press :kbd:`Ctrl+Enter` and you will be able to type in the file name.
Selecting directories
---------------------------
This kitten can also be used to select directories,
for an :guilabel:`Open directory` type of dialog using :option:`--mode <kitty +kitten
choose_files --mode>`:code:`=dir`. Once you have changed to the directory
you want, press :kbd:`Ctrl+Enter` to accept it. Or if you are in a parent
directory you can select a descendant directory by pressing :kbd:`Enter`, the
same as you would for selecting a file to open.
Configuration
------------------------
You can configure various aspects of the kitten's operation by creating a
:file:`choose-files.conf` in your :ref:`kitty config folder <confloc>`.
See below for the supported configuration directives.
.. include:: /generated/conf-kitten-choose_files.rst
.. include:: /generated/cli-kitten-choose_files.rst

View File

@@ -118,11 +118,11 @@ that is, they are used to set the variable value for some font characteristic.
<https://harfbuzz.github.io/harfbuzz-hb-common.html#hb-feature-from-string>`__
``system``
This can be used to pass an arbitrary string, usually a family or full name
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. Its value is used to set the value for
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
from typing import List
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
# the overlay window when the kitten is launched
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
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
w = boss.window_id_map.get(target_window_id)
if w is not None:
@@ -59,7 +60,7 @@ would pass to ``kitten @``. For example:
.. 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
w = boss.window_id_map.get(target_window_id)
if w is not None:
@@ -100,18 +101,19 @@ like. For example:
.. code-block:: py
from typing import List
from kitty.boss import Boss
# in main, STDIN is for the kitten process and will contain
# the contents of the screen
def main(args: list[str]) -> str:
def main(args: List[str]) -> str:
return sys.stdin.read()
# in handle_result, STDIN is for the kitty process itself, rather
# than the kitten process and should not be read from.
from kittens.tui.handler import result_handler
@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
@@ -122,9 +124,9 @@ the table below:
.. table:: Types of input to kittens
:align: left
=========================== ========================================================================================
=========================== =======================================================================================================
Keyword Type of :file:`STDIN` input
=========================== ========================================================================================
=========================== =======================================================================================================
``text`` Plain text of active window
``ansi`` Formatted text of active window
``screen`` Plain text of active window with line wrap markers
@@ -141,7 +143,7 @@ the table below:
``output-screen-ansi`` Formatted text of the output from the last run command with wrap markers
``selection`` The text currently selected with the mouse
=========================== ========================================================================================
=========================== =======================================================================================================
In addition to ``output``, that gets the output of the last run command,
``last_visited_output`` gives the output of the command last jumped to
@@ -169,14 +171,15 @@ Create a Python file in the :ref:`kitty config directory <confloc>`,
.. code-block:: py
from typing import List
from kitty.boss import Boss
def main(args: list[str]) -> str:
def main(args: List[str]) -> str:
pass
from kittens.tui.handler import result_handler
@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
if tab is not None:
if tab.current_layout.name == 'stack':
@@ -228,56 +231,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.
.. _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
--------------------
@@ -298,6 +251,107 @@ So if you run kitty from another kitty instance, the output will be visible
in the first kitty instance.
Adding options to kittens
----------------------------
If you would like to use kitty's config framework to make your kittens
configurable, you will need some boilerplate. Put the following files in the
directory of your kitten.
:file:`kitten_options_definition.py`
.. code-block:: python
from kitty.conf.types import Action, Definition
definition = Definition(
'!kitten_options_utils',
Action(
'map', 'parse_map',
{'key_definitions': 'kitty.conf.utils.KittensKeyMap'},
['kitty.types.ParsedShortcut', 'kitty.conf.utils.KeyAction']
),
)
agr = definition.add_group
egr = definition.end_group
opt = definition.add_option
map = definition.add_map
# main options {{{
agr('main', 'Main')
opt('some_option', '33',
option_type='some_option_parser',
long_text='''
Help text for this option
'''
)
egr() # }}}
# shortcuts {{{
agr('shortcuts', 'Keyboard shortcuts')
map('Quit', 'quit q quit')
egr() # }}}
:file:`kitten_options_utils.py`
.. code-block:: python
from kitty.conf.utils import KittensKeyDefinition, key_func, parse_kittens_key
func_with_args, args_funcs = key_func()
FuncArgsType = Tuple[str, Sequence[Any]]
def some_option_parser(val: str) -> int:
return int(val) + 3000
def parse_map(val: str) -> Iterable[KittensKeyDefinition]:
x = parse_kittens_key(val, args_funcs)
if x is not None:
yield x
Then run::
kitty +runpy 'from kitty.conf.generate import main; main()' /path/to/kitten_options_definition.py
You can parse and read the options in your kitten using the following code:
.. code-block:: python
from .kitten_options_types import Options, defaults
from kitty.conf.utils import load_config as _load_config, parse_config_base
from typing import Optional, Iterable, Dict, Any
def load_config(*paths: str, overrides: Optional[Iterable[str]] = None) -> Options:
from .kitten_options_parse import (
create_result_dict, merge_result_dicts, parse_conf_item
)
def parse_config(lines: Iterable[str]) -> Dict[str, Any]:
ans: Dict[str, Any] = create_result_dict()
parse_config_base(
lines,
parse_conf_item,
ans,
)
return ans
overrides = tuple(overrides) if overrides is not None else ()
opts_dict, found_paths = _load_config(defaults, parse_config, merge_result_dicts, *paths, overrides=overrides)
opts = Options(opts_dict)
opts.config_paths = found_paths
opts.all_config_paths = paths
opts.config_overrides = overrides
return opts
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
options and keyboard shortcuts.
Developing builtin kittens for inclusion with kitty
----------------------------------------------------------
@@ -317,12 +371,6 @@ Kittens created by kitty users
`smart-scroll <https://github.com/yurikhan/kitty-smart-scroll>`_
Makes the kitty scroll bindings work in full screen applications
`kitty-tab-switcher <https://github.com/OsiPog/kitty-tab-switcher>`__
Fuzzy finder for kitty tabs with previews
`gattino <https://github.com/salvozappa/gattino>`__
Integrate kitty with an LLM to convert plain language prompts into shell commands.
:iss:`insert password <1222>`
Insert a password from a CLI password manager, taking care to only do it at
a password prompt.

View File

@@ -1,126 +0,0 @@
Using terminal programs to provide Linux desktop components
===============================================================
.. only:: man
Overview
--------------
.. versionadded:: 0.43.0
Power users of terminals on Linux also often like to use bare bones window
managers instead of full fledged desktop environments. This kitten helps
provide parts of the desktop environment that are missing from such setups,
and does so using keyboard friendly, terminal first UI components. Some of its
features are:
* Replace the typical File Open/Save dialogs used in GUI programs with the
fast and keyboard centric :doc:`choose-files </kittens/choose-files>` kitten
running in a semi-transparent kitty overlay.
* Allow simple command line based management of the desktop light/dark modes.
How to install
-------------------
.. note::
This kitten relies on the :doc:`panel kitten </kittens/panel>`
under the hood to supply UI components. Check :ref:`the documentation <panel_compat>`
of that kitten to see if your window manager works with it.
First, run::
kitten desktop-ui enable-portal
Then, set the following two environment variables, *system wide*, that means in
:file:`/etc/environment` or the equivalent for your distribution::
QT_QPA_PLATFORMTHEME=xdgdesktopportal
GTK_USE_PORTAL=1
Finally, reboot. Now, when you open a file dialog in most GUI applications, it
should open the :doc:`choose-files kitten </kittens/choose-files>` instead
of a normal file open dialog. You can change the current light/dark mode of
your desktop by running::
kitten desktop-ui set-color-scheme dark
kitten desktop-ui set-color-scheme light
Check the current value using::
dbus-send --session --print-reply --dest=org.freedesktop.portal.Desktop /org/freedesktop/portal/desktop org.freedesktop.portal.Settings.Read string:org.freedesktop.appearance string:color-scheme
How it works
----------------
Modern Linux desktops have so called `portals
<https://flatpak.github.io/xdg-desktop-portal/docs/index.html>`__ that were
invented for sandboxed applications and provide various facilities to such
applications over DBUS, including file open dialogs, common desktop settings,
etc. This kitten works by implementing a backend for some of these services.
Normal GUI applications can then be told to make use of these services, thereby
allowing us to replace parts of the desktop experience as needed.
There are multiple competing implementations of the backends. Each desktop
environment like KDE or GNOME has it's own backend and many window managers
provide implementations for some backends as well. Service discovery and
configuring which backend to use happens via the :file:`xdg-desktop-portal`
program, usually found at :file:`/usr/lib/xdg-desktop-portal`.
It can be configured by files in :file:`~/.local/share/xdg-desktop-portal`. See
`man portals.conf <https://man.archlinux.org/man/portals.conf.5>`__. The
``kitten desktop-ui enable-portal`` command takes care of the setup for you
automatically. If you want to customize exactly which services to use this
kitten for, run the command and then edit the conf file that the command says
it has patched.
Troubleshooting
-------------------
First, ensure that DBUS is able to auto-start the kitten when it is needed. If
the kitten is not already running, try the following command::
dbus-send --session --print-reply --dest=org.freedesktop.impl.portal.desktop.kitty \
/net/kovidgoyal/kitty/portal org.freedesktop.DBus.Properties.GetAll \
string:net.kovidgoyal.kitty.settings
If DBUS is able to start the kitten or if it is already running it will print
out the version property, otherwise it will fail with an error. If it fails,
check the file
:file:`~/.local/share/dbus-1/services/org.freedesktop.impl.portal.desktop.kitty.service`
that should have been created by the ``enable-portal`` command. It's ``Exec``
key must point to the full path to the kitten executable.
Next, check that the XDG portal system is actually using this kitten for its
settings backend. Run::
dbus-send --session --print-reply --dest=org.freedesktop.portal.Desktop \
/org/freedesktop/portal/desktop org.freedesktop.portal.Settings.Read \
string:net.kovidgoyal.kitty string:status
If this returns a reply then the kitten is being used, as expected. If it
returns a not found error, then some other backend is being used for settings.
Read the ``portals.conf`` man page and run::
/usr/lib/xdg-desktop-portal -r v
this will output a lot of debug information, which should tell you which
backend is chosen for which service. Read the debug output carefully to
determine why the kitten is not being selected.
If some GUI applications are not using the choose-files kitten for their file
select dialogs, then make sure the environment variables mentioned above are
set, you can also try running the the GUI application with them set explicitly,
as::
QT_QPA_PLATFORMTHEME=xdgdesktopportal GTK_USE_PORTAL=1 my-gui-app
Note that not all applications use portals, so if some particular application
is failing to use the portal but others work, report the issue to that
applications' developers.

View File

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

View File

@@ -8,214 +8,52 @@ Draw a GPU accelerated dock panel on your desktop
Overview
--------------
.. include:: ../quake-screenshots.rst
Draw the desktop wallpaper or docks and panels using arbitrary
terminal programs, For example, have `btop
<https://github.com/aristocratos/btop>`__ or `cava
<https://github.com/karlstav/cava/>`__ be your desktop wallpaper.
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
terminal program.
It is useful for showing status information or notifications on your desktop
using terminal programs instead of GUI toolkits.
.. figure:: ../screenshots/panel.png
:alt: Screenshot, showing a sample panel
:align: center
:width: 100%
The screenshot to the side shows some uses of the panel kitten to draw various
desktop components such as the background, a quick access floating terminal and
a dock panel showing system information (Linux only).
Screenshot, showing a sample panel
.. versionadded:: 0.42.0
Support for macOS, see :ref:`compatibility matrix <panel_compat>` for details.
and X11 (background and overlay).
The screenshot above shows a sample panel that displays the current desktop and
window title as well as miscellaneous system information such as network
activity, CPU load, date/time, etc.
.. versionadded:: 0.34.0
Support for Wayland
Support for Wayland. See :ref:`below <panel_compat>` for which
Wayland compositors work.
.. note::
This kitten currently only works on X11 desktops and Wayland compositors
that support the `wlr layer shell protocol
<https://wayland.app/protocols/wlr-layer-shell-unstable-v1#compositor-support>`__
(which is almost all of them except the, as usual, crippled GNOME).
Using this kitten is simple, for example::
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
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
you like, as demonstrated in the screenshots.
you like, as demonstrated in the screenshot above.
If you are on Wayland or macOS, you can, for instance, run::
If you are on Wayland, you can, for instance run::
kitten panel --edge=background htop
kitty +kitten panel --edge=background htop
to display ``htop`` as your desktop background. Remember this works in everything
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:
.. _panel_projects:
* `kitty panel <https://github.com/5hubham5ingh/kitty-panel>`__
* `pawbar <https://github.com/codelif/pawbar>`__
.. _remote_control_panel:
Controlling panels via remote control
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can control panels via the kitty :doc:`remote control </remote-control>` facility. Create a panel
with remote control enabled::
kitten panel -o allow_remote_control=socket-only --lines=2 \
--listen-on=unix:/tmp/panel kitten run-shell
Now you can control this panel using remote control, for example to show/hide
it, use::
kitten @ --to=unix:/tmp/panel resize-os-window --action=toggle-visibility
To move the panel to the bottom of the screen and increase its height::
kitten @ --to=unix:/tmp/panel resize-os-window --action=os-panel \
--incremental edge=bottom lines=4
To create a new panel running the program top, in the same instance
(like creating a new OS window)::
kitten @ --to=unix:/tmp/panel launch --type=os-panel --os-panel edge=top \
--os-panel lines=8 top
.. include:: ../generated/cli-kitten-panel.rst
.. _quake_ss:
How the screenshots were generated
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The system statistics in the background were created using::
kitten panel --edge=background -o background_opacity=0.2 -o background=black btop
This creates a kitty background window and inside it runs the `btop
<https://github.com/aristocratos/btop>`__ program to display the statistics.
The floating quick access window was created by running::
kitten quick-access-terminal kitten run-shell \
zsh -c 'printf "\e]66;s=4;Quick access kitty in Hyprland\a\n\n\n\nAlso uses kitty to draw desktop background\n"'
This starts the quick access window and inside it runs ``kitten run-shell``, which
in turn first runs ``zsh`` to print out the message and then starts the users login
shell.
The Linux dock panel was::
wm bar
This is a custom program I wrote for my personal use. It uses kitty's kitten
infrastructure to implement the bar in a `few hundred lines of code
<https://github.com/kovidgoyal/wm/blob/master/bar/main.go>`__.
This was designed for my personal use only, but, there are :ref:`public projects implementing
general purpose panels using kitty <panel_projects>`.
.. _panel_compat:
Compatibility with various platforms
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. only:: man
See the HTML documentation for the compatibility matrix.
.. only:: not man
Generated with the help of the :file:`panels.py` test script.
.. tab:: Wayland
Below is a list of the status of various Wayland compositors. The panel kitten
relies of the `wlr layer shell protocol
<https://wayland.app/protocols/wlr-layer-shell-unstable-v1#compositor-support>`__,
which is technically supported by almost all Wayland compositors, but the
implementation in some of them is quite buggy.
🟢 **Hyprland**
Fully working, no known issues
🟢 **labwc**
Fully working, no known issues
🟢 **niri**
Fully working, no known issues
🟢 **river**
Fully working, no known issues
🟢 **Xfce**
Fully working, no known issues
🟠 **KDE** (kwin)
Mostly working, except that clicks outside background panels cause kwin to :iss:`erroneously hide the panel <8715>`. KDE uses an `undocumented mapping <https://invent.kde.org/plasma/kwin/-/blob/3dc5cee6b34792486b343098e55e7f2b90dfcd00/src/layershellv1window.cpp#L24>`__ under Wayland to set the window type from the :code:`kitten panel --app-id` flag. You might want to use :code:`--app-id=dock` so that KDE treats the window as a dock panel, and disables window appearing/disappearing animations for it.
🟠 **Sway**
Renders its configured background over the background window instead of
under it. This is because it uses the wlr protocol for backgrounds itself.
🔴 **GNOME** (mutter)
Does not implement the wlr protocol at all, nothing works.
.. tab:: macOS
Mostly everything works, with the notable exception that dock panels do not
prevent other windows from covering them. This is because Apple does not
provide and way to do this in their APIs.
.. tab:: X11
Support is highly dependent on the quirks of individual window
managers. See the matrix below:
.. list-table:: Compatibility matrix
:header-rows: 1
:stub-columns: 1
* - WM
- Desktop
- Dock
- Quick
- Notes
* - KDE
- 🟠
- 🟢
- 🟢
- transparency does not work for :option:`--edge=background <--edge>`
* - GNOME
- 🟢
- 🟢
- 🟢
-
* - XFCE
- 🟢
- 🟢
- 🟢
-
* - i3
- 🔴
- 🟠
- 🔴
- only top and bottom dock panels, without transparency
* - xmonad
- 🔴
- 🔴
- 🔴
- doesn't support the needed NET_WM protocols

View File

@@ -1,84 +0,0 @@
.. _quake:
Make a Quake like quick access terminal
====================================================================================================
.. highlight:: sh
.. only:: man
Overview
--------------
.. include:: ../quake-screenshots.rst
.. versionadded:: 0.42.0
See :ref:`here for what platforms it works on <panel_compat>`.
This kitten can be used to make a quick access terminal, that appears and
disappears at a key press. To do so use the following command:
.. code-block:: sh
kitten quick-access-terminal
Run this command in a terminal, and a quick access kitty window will show up at
the top of your screen. Run it again, and the window will be hidden.
To make the terminal appear and disappear at a key press:
.. |macOs| replace:: :guilabel:`System Preferences->Keyboard->Keyboard Shortcuts->Services->General`
.. only:: not man
.. tab:: Linux
Simply bind the above command to some key press in your window manager or desktop
environment settings and then you have a quick access terminal at a single key press.
.. tab:: macOS
In kitty, run the above command to show the quick access window, then close
it by running the command again or pressing :kbd:`ctrl+d`. Now go to |macOS| and set a shortcut for
the :guilabel:`Quick access to kitty` entry.
.. only:: man
In Linux, simply assign the above command to a global shortcut in your
window manager. In macOS, go to |macOS| and set a shortcut
for the :guilabel:`Quick access to kitty` entry.
Configuration
------------------------
You can configure the appearance and behavior of the quick access window
by creating a :file:`quick-access-terminal.conf` file in your
:ref:`kitty config folder <confloc>`. In particular, you can use the
:opt:`kitty_conf <kitten-quick_access_terminal.kitty_conf>` option to change
various kitty settings, just for the quick access window.
.. note::
This kitten uses the :doc:`panel kitten </kittens/panel>` under the
hood. You can use the :ref:`techniques described there <remote_control_panel>`
for remote controlling the quick access window, remember to add
``kitty_override allow_remote_control=socket-only`` and ``kitty_override
listen_on=unix:/tmp/whatever`` to
:file:`quick-access-terminal.conf`.
See below for the supported configuration directives:
.. include:: /generated/conf-kitten-quick_access_terminal.rst
.. include:: /generated/cli-kitten-quick_access_terminal.rst
Sample quick-access-terminal.conf
---------------------------------------
You can download a sample :file:`quick-access-terminal.conf` file with all default settings and
comments describing each setting by clicking: :download:`sample quick-access-terminal.conf
</generated/conf/quick_access_terminal.conf>`.

View File

@@ -47,48 +47,11 @@ Once that's done, the kitten sends kitty a signal to make it reload its config.
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
``include`` it into kitty.conf. The include should be places 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, and also all background image settings,
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
-----------------------

View File

@@ -13,9 +13,6 @@ Extend with kittens
kittens/themes
kittens/choose-fonts
kittens/hints
kittens/quick-access-terminal
kittens/choose-files
kittens/panel
kittens/remote_file
kittens/hyperlinked_grep
kittens/transfer
@@ -54,20 +51,6 @@ Some prominent kittens:
filenames, words, lines, etc. from the terminal screen.
:doc:`Quick access terminal <kittens/quick-access-terminal>`
Get access to a quick access floating, semi-transparent kitty window
with a single keypress.
:doc:`Panel <kittens/panel>`
Draw the desktop wallpaper or docks and panels using arbitrary
terminal programs.
:doc:`Choose files <kittens/choose-files>`
Preview and select files at the speed of thought
:doc:`Remote file <kittens/remote_file>`
Edit, open, or download remote files over SSH easily, by simply clicking on
the filename.
@@ -96,6 +79,11 @@ Some prominent kittens:
configuration to the remote host.
:doc:`Panel <kittens/panel>`
Draw a GPU accelerated dock panel on your desktop showing the output from an
arbitrary terminal program.
:doc:`Clipboard <kittens/clipboard>`
Copy/paste to the clipboard from shell scripts, even over SSH.

View File

@@ -122,70 +122,45 @@ create :file:`~/.config/kitty/mywatcher.py` and use :option:`launch --watcher` =
.. code-block:: python
# ~/.config/kitty/mywatcher.py
from typing import Any
from typing import Any, Dict
from kitty.boss import Boss
from kitty.window import Window
def on_load(boss: Boss, 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 initialization/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:
def on_resize(boss: Boss, window: Window, data: Dict[str, Any]) -> None:
# 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
...
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
# 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
# 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
# data will contain title and from_child. from_child will be True
# when a title change was requested via escape code from the program
# 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
# data will contain is_start, cmdline 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
...
def on_tab_bar_dirty(boss: Boss, window: Window, data: dict[str, Any]) -> None:
# called when any changes happen to the tab bar, such a new tabs being
# created, tab titles changing, tabs moving, etc. Useful to display the
# tab bar externally to kitty. This is called even if the tab bar is
# hidden. Note that this is called only in *global watchers*, that is
# watchers defined in kitty.conf or using the --watcher command line
# flag. data contains tab_manager which is the object responsible for
# managing all tabs in a single OS Window.
...
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
that contains event dependent data. You have full access to kitty internals in
@@ -196,7 +171,7 @@ would pass to ``kitten @``. For example:
.. code-block:: python
def on_resize(boss: Boss, window: Window, data: dict[str, Any]) -> None:
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'))

View File

@@ -179,24 +179,16 @@ define a few extra key bindings in :file:`kitty.conf`::
map ctrl+up neighboring_window up
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
in a split using the ``rotate`` action with an argument of ``180`` and rotate
and swap with an argument of ``270``.
This layout takes one option, ``split_axis`` that controls whether new windows
are placed into vertical or horizontal splits when a :option:`--location
<launch --location>` is not specified. A value of ``horizontal`` (same as
``--location=vsplit``) means when a new split is created the two windows will
be placed side by side and a value of ``vertical`` (same as
``--location=hsplit``) means the two windows will be placed one on top of the
other. A value of ``auto`` means the axis of the split is chosen automatically
(same as ``--location=split``). By default::
are placed into vertical or horizontal splits when a :option:`--location <launch
--location>` is not specified. A value of ``horizontal`` (same as
``--location=vsplit``) means when a new split is created the two windows will be
placed side by side and a value of ``vertical`` (same as ``--location=hsplit``)
means the two windows will be placed one on top of the other. By default::
enabled_layouts splits:split_axis=horizontal

View File

@@ -194,15 +194,15 @@ running an editor::
map --when-focus-on var:in_editor kitty_mod+c
In order to make this work, you need to configure your editor as shown below:
In order to make this work, you need to configure your editor as show below:
.. tab:: vim
In :file:`~/.vimrc` add:
.. code-block:: vim
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_ti = &t_ti . "\\033]1337;SetUserVar=in_editor=MQo\\007"
let &t_te = &t_te . "\\033]1337;SetUserVar=in_editor\\007"
.. tab:: neovim
@@ -210,25 +210,17 @@ In order to make this work, you need to configure your editor as shown below:
.. code-block:: lua
vim.api.nvim_create_autocmd({ "VimEnter", "VimResume", "UIEnter" }, {
vim.api.nvim_create_autocmd({ "VimEnter", "VimResume" }, {
group = vim.api.nvim_create_augroup("KittySetVarVimEnter", { clear = true }),
callback = function()
if vim.api.nvim_ui_send then
vim.api.nvim_ui_send("\x1b]1337;SetUserVar=in_editor=MQo\007")
else
io.stdout:write("\x1b]1337;SetUserVar=in_editor=MQo\007")
end
io.stdout:write("\x1b]1337;SetUserVar=in_editor=MQo\007")
end,
})
vim.api.nvim_create_autocmd({ "VimLeave", "VimSuspend" }, {
group = vim.api.nvim_create_augroup("KittyUnsetVarVimLeave", { clear = true }),
callback = function()
if vim.api.nvim_ui_send then
vim.api.nvim_ui_send("\x1b]1337;SetUserVar=in_editor=MQo\007")
else
io.stdout:write("\x1b]1337;SetUserVar=in_editor\007")
end
io.stdout:write("\x1b]1337;SetUserVar=in_editor\007")
end,
})
@@ -238,15 +230,6 @@ copying to clipboard. In the editor, you can map it to copy to the clipboard,
thereby allowing use of a common shortcut both inside and outside the editor
for copying to clipboard.
.. note::
When using multi-key mappings, of the form :kbd:`k1>k2` or similar, the
condition applies to the first key and you can have only one condition per
key, the last in kitty.conf wins. In particular, this means you cannot have
multiple conditions applying to multi-key mappings with the same first key
and you cannot have mappings with and without conditions applying to multi-keys
with the same first key.
Sending arbitrary text or keys to the program running in kitty
--------------------------------------------------------------------------------
@@ -327,7 +310,7 @@ below::
# Beep on unknown keys
map --new-mode XXX --on-unknown beep ...
# Ignore unknown keys silently
# Ingore unknown keys silently
map --new-mode XXX --on-unknown ignore ...
# Beep and exit the keyboard mode on unknown key
map --new-mode XXX --on-unknown end ...

View File

@@ -1,60 +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.
.. _mouse_leave_window:
Reporting when the mouse leaves the window
----------------------------------------------
kitty extends the SGR Pixel mouse reporting protocol created by xterm to
also report when the mouse leaves the window. This event is delivered
encoded as a normal SGR pixel event except that the eight bit is set on the
first number. Additionally, bit 5 is set to indicate this is a motion related event.
The remaining bits 1-7 (except 5) are used to encode button and modifier information.
When bit 8 is set it means the event is a mouse has left the window event,
and all other bits should be ignored. The pixel position values must also
be ignored as they may not be accurate.
An escape code to move the contents of the screen into the scrollback
-------------------------------------------------------------------------------------
The escape code is ``\x1b [ 22 J`` (ignoring spaces present for clarity). It
moves all screen contents (text and images) into the scrollback leaving the
screen in the same state as it would be if the standard screen clear escape
code had been used ``\x1b [ 2 J``.
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,251 +0,0 @@
The multiple cursors protocol
==============================================
.. versionadded:: 0.43.0
.. warning::
This protocol is under public discussion in :iss:`8927`. It is subject to
change until that discussion is completed.
Many editors support something called *multiple cursors* in which you can make
the same changes at multiple locations in a file and the editor shows you
cursors at each of the locations. In a terminal context editors typically
implement this by showing some Unicode glyph at each location instead of the
actual cursor. This is sub-optimal since actual cursors implemented by the
terminal have many niceties like smooth animation [anim]_, auto adjust colors [rv]_,
etc. To address this and other use cases, this protocol allows terminal programs to
request that the terminal display multiple cursors at specific locations on the
screen.
Quickstart
----------------
An example, showing how to use the protocol:
.. code-block:: sh
# Show cursors of the same shape as the main cursor at y=4, x=5
printf "\e[>29;2:4:5 q"
# Show more cursors on the seventh line, of various shapes, the underline shape is shown twice
printf "\e[>1;2:7:1 q\e[>2;2:7:3 q\e[>3;2:7:5;2:7:7 q"
The escape code to show a cursor has the following structure (ignore spaces
they are present for readability only)::
CSI > SHAPE;CO-ORD TYPE : CO-ORDINATES ; CO-ORD TYPE : CO-ORDINATES ... TRAILER
Here ``CSI`` is the two bytes ESC (``0x1b``) and [ (``0x5b``). ``SHAPE`` can be
one of:
* ``0``: No cursor
* ``1``: Block cursor
* ``2``: Beam cursor
* ``3``: Underline cursor
* ``29``: Follow the shape of the main cursor
* ``30``: Change the color of text under extra cursors
* ``40``: Change the color of extra cursors
* ``100``: Used for querying currently set cursors
``CO-ORD TYPE`` can be one of:
* ``0``: This refers to the position of the main cursor and has no following
co-ordinates.
* ``2``: In this case the following co-ordinates are pairs of numbers pointing
to cells in the form ``y:x`` with the origin in the top left corner at
``1,1``. There can be any number of pairs, the terminal must treat each pair
as a new location to set a cursor.
* ``4``: In this case the following co-ordinates are sets of four numbers that
define a rectangle in the same co-ordinate system as above of the form:
``top:left:bottom:right``. The shape is set on every cell in the rectangle
from the top left cell to the bottom right cell, inclusive. If no numbers
are provided, the rectangle is the full screen. There can be any number of
rectangles, the terminal must treat each set of four numbers as a new
rectangle.
The sequence of ``CO-ORD TYPE : CO-ORDINATES`` can be repeated any number of
times separated by ``;``. The ``SHAPE`` will be set on the cells indicated by
each such group. For example: ``-1;2:3:4;4:5:6:7:8`` will set the shape ``-1``
at the cell ``(3, 2)`` and in the rectangle ``(6, 5)`` to ``(8, 7)`` inclusive.
Finally, the ``TRAILER`` terminates the sequence and is the bytes SPACE
(``0x20``) and q (``0x71``).
Terminals **must** ignore cells that fall outside the screen. That means, for
rectangle co-ordinates only the intersection of the rectangle with the screen
must be considered, and point co-ordinates that fall outside of the screen are
simply ignored, with no effect.
Terminals **must** ignore extra co-ordinates, that means if an odd number of
co-ordinates are specified for type ``2`` the last co-ordinate is ignored.
Similarly for type ``4`` if the number of co-ordinates is not a multiple of
four, the last ``1 <= n <= 3`` co-ordinates are ignored, as if they were not
specified.
Querying for support
-------------------------
A terminal program can query the terminal emulator for support of this
protocol by sending the escape code::
CSI > TRAILER
In this case a supporting terminal must reply with::
CSI > 1;2;3;29;30;40;100;101 TRAILER
Here, the list of numbers indicates the cursor shapes and other operations
the terminal supports and can be any subset of the above. No numbers
indicates the protocol is not supported. To avoid having to wait with a
timeout for a response from the terminal, the client should send this
query code immediately followed by a request for the
`primary device attributes <https://vt100.net/docs/vt510-rm/DA1.html>`_.
If the terminal responds with an answer for the device attributes without
an answer for the *query* the terminal emulator does not support this protocol at all.
Terminals **must** respond to these queries in FIFO order, so that
multiplexers that split a single screen know which split to send responses too.
Clearing previously set multi-cursors
------------------------------------------
The cursor at a cell is cleared by setting its shape to ``0``.
The most common operation is to clear all previously set multi-cursors. This is
easily done using the *rectangle* co-ordinate system above, like this::
CSI > 0;4 TRAILER
For more precise control different co-ordinate types can be used. This is
particularly important for multiplexers that split up the screen and therefore
need to re-write these escape codes.
.. _extra_cursor_color:
Changing the color of extra cursors
---------------------------------------
In order to visually distinguish extra cursors from the main cursor, it is
possible to specify a color pair for extra cursors. Note that for performance
reasons, there is only a single color pair that all extra cursors share.
The color pair consists of the cursor color and the color for text in the cell
the cursor is on.
To change this color pair use an escape code of the form::
CSI > WHICH ; COLOR_SPACE : COLOR_PARAMETER1 : COLOR_PARAMETER2 : ... TRAILER
Here, ``WHICH`` is ``30`` to set the color of text under the cursor and ``40``
to set the color of the cursor itself (these numbers mimic the SGR codes for
foreground and background respectively).
The ``COLOR_SPACE`` parameter sets the type of color, it can take values:
``0`` - unset color is same as for main cursor. No color parameters.
``1`` - *special* which typically means some kind of reverse video effect, see below
``2`` - sRGB color, with three color parameters, red, green and blue as numbers
from 0 to 255
``5`` - Indexed color with one color parameter which is an index into the color
table from 0 to 255
When the cursor color is set to *special* via ``40`` it means the block cursor
must be rendered with a reverse video effect where the cursor color becomes the
foreground color of the cell under the cursor and the foreground color of the
cell becomes its background color. Implementations are free to adjust these
colors to ensure suitable contrast levels. In this case the text color set by
``30`` must be ignored.
When the cursor color is not set to *special* but the text color via ``30`` is
set to special, then that means the foreground color of the cell with the
cursor must be changed to its background color for a partial reverse video
effect.
When unset, aka, set to ``0`` the cursors must be the same color as the main
cursor. In particular if the main color is using a reverse video effect, the
extra cursors must use the exact same colors as the main cursor, not the colors
of the cells they are on.
Querying for already set cursors
--------------------------------------
Programs can ask the terminal what extra cursors are currently set, by sending
the escape code::
CSI > 100 TRAILER
The terminal must respond with **one** escape code::
CSI > 100; SHAPE:CO-ORDINATE TYPE:CO-ORDINATES ; ... TRAILER
Here, the ``SHAPE:CO-ORDINATE TYPE:CO-ORDINATES`` block can be repeated any
number of times, separated by ``;``. This response gives the set of shapes and
positions currently active. If no cursors are currently active, there will be
no blocks, just an empty response of the form::
CSI > 100 TRAILER
Again, terminals **must** respond in FIFO order so that multiplexers know where
to direct the responses.
Querying for extra cursor colors
-------------------------------------
Programs can ask the terminal what cursor colors are currently set, by sending
escape code::
CSI > 101 TRAILER
The terminal must respond with **one** escape code::
CSI > 101 ; 30 : COLOR_SPACE : COLOR_PARAMETERS ; 40 : COLOR_SPACE : COLOR_PARAMETERS TRAILER
The number and type of ``COLOR_PARAMETERS`` depends on the preceding
``COLOR_SPACE`` and can be omitted for some ``COLOR_SPACE`` values. See the
section :ref:`extra_cursor_color` for details.
Interaction with other terminal controls and state
-------------------------------------------------------
**The main cursor**
The extra cursors must all have the same color and opacity and blink state
as the main cursor. The main cursor's visibility must not affect the
visibility of the extra cursors. Their visibility and shape are controlled
only by this protocol.
**Clearing the screen**
The escape codes used to clear the screen (`ED <https://vt100.net/docs/vt510-rm/ED.html>`__)
with parameters 2, 3 and 22 must remove all extra cursors,
this is so that the clear command can be used by users to clear the screen of extra cursors.
**Reset***
This must remove all extra cursors.
**Alternate screen***
Switching between the main and alternate screens must remove all extra
cursors.
**Scrolling**
The index (IND) and reverse index (RI) escape codes that cause screen
contents to scroll into scrollback or off screen must not affect
the extra cursors in any way. They remain at exactly the same position.
It is up to applications to manage extra cursor positions when using these
escape codes if needed. There are not a lot of use cases for scrolling
extra cursors with screen content, since extra cursors are meant to be
ephemeral and on screen only, not in scrollback. This allows terminals
to avoid the extra overhead of adjusting positions of the extra cursors
on every scroll.
Footnotes
-------------
.. [anim] kitty allows the cursor blink to be :opt:`animated
<cursor_blink_interval>` using any CSS easing function. This cannot be
implemented using fake cursors.
.. [rv] kitty has a special "reverse video" color mode for cursors where the
color of the cursor and the text under the cursor is adjusted based on the
color of the cell under the cursor. This also cannot be implemented using
fake cursors.

View File

@@ -3,7 +3,7 @@ Scripting the mouse click
|kitty| has support for :term:`terminal hyperlinks <hyperlinks>`. These are
generated by many terminal programs, such as ``ls``, ``gcc``, ``systemd``,
:ref:`tool_mcat`, etc. You can customize exactly what happens when clicking on
:ref:`tool_mdcat`, etc. You can customize exactly what happens when clicking on
these hyperlinks in |kitty|.
You can tell kitty to take arbitrarily many, complex actions when a link is
@@ -48,7 +48,7 @@ action per entry if you like, for example:
action change_font_size current -2
In the launch specification you can expand environment variables, as shown in
In the action specification you can expand environment variables, as shown in
the examples above. In addition to regular environment variables, there are
some special variables, documented below:
@@ -126,12 +126,11 @@ lines. The various available criteria are:
.. _launch_actions:
Scripting the opening of files with kitty
Scripting the opening of files with kitty on macOS
-------------------------------------------------------
On macOS you can use :guilabel:`Open With` in Finder or drag and drop files and
URLs onto the kitty dock icon to open them with kitty. Similarly on Linux, you
can associate certain files types to open in kitty. The default actions are:
URLs onto the kitty dock icon to open them with kitty. The default actions are:
* Open text files in your editor and images using the icat kitten.
* Run shell scripts in a shell

View File

@@ -96,16 +96,12 @@ used to create programs that leverage kitty's powerful features, for example,
You can :doc:`create your own kittens to scratch your own itches
<kittens/custom>`.
For a list of all the builtin kittens, run ``kitten`` in kitty, or to browse
some of the more prominent ones, see :ref:`see here <kittens>`.
For a list of all the builtin kittens, :ref:`see here <kittens>`.
Additionally, you can use the :ref:`watchers <Watchers>` framework
to create Python scripts that run in response to various events such as windows
being resized, closing, having their titles changed, etc.
Finally, there is remote control which allows you to control kitty from
anywhere, even across a network! See below for more about remote control.
.. toctree::
:hidden:
@@ -128,14 +124,84 @@ started.
remote-control
Sessions
.. _sessions:
Startup Sessions
------------------
You can control the :term:`tabs <tab>`, :term:`kitty window <window>` layout,
working directory, startup programs, etc. by creating a *session* file and using
the :option:`kitty --session` command line flag or the :opt:`startup_session`
option in :file:`kitty.conf`. You can also easily switch between sessions with
a keypress. See :doc:`sessions` for details.
option in :file:`kitty.conf`. An example, showing all available commands:
.. code-block:: session
# Set the layout for the current tab
layout tall
# Set the working directory for windows in the current tab
cd ~
# Create a window and run the specified command in it
launch zsh
# Create a window with some environment variables set and run vim in it
launch --env FOO=BAR vim
# Set the title for the next window
launch --title "Chat with x" irssi --profile x
# Create a new tab
# The part after new_tab is the optional tab title which will be displayed in
# the tab bar, if omitted, the title of the active window will be used instead.
new_tab my tab
cd ~/somewhere
# Set the layouts allowed in this tab
enabled_layouts tall,stack
# Set the current layout
layout stack
launch zsh
# Create a new OS window
# Any definitions specified before the first new_os_window will apply to first OS window.
new_os_window
# Set new window size to 80x24 cells
os_window_size 80c 24c
# Set the --class for the new OS window
os_window_class mywindow
# Change the OS window state to normal, fullscreen, maximized or minimized
os_window_state normal
launch sh
# Resize the current window (see the resize_window action for details)
resize_window wider 2
# Make the current window the active (focused) window in its tab
focus
# Make the current OS Window the globally active window (not supported on Wayland)
focus_os_window
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::
The :doc:`launch <launch>` command when used in a session file cannot create
new OS windows, or tabs.
.. note::
Environment variables of the for :code:`${NAME}` or :code:`$NAME` are
expanded in the session file, except in the *arguments* (not options) to the
launch command.
Creating tabs/windows
@@ -176,7 +242,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
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::
:hidden:
@@ -201,10 +269,9 @@ The scrollback buffer
-----------------------
|kitty| supports scrolling back to view history, just like most terminals. You
can use either keyboard shortcuts or the mouse scroll wheel to do so. |kitty|
displays an interactive :opt:`scrollbar` along the right edge
of the window that shows your current position in the scrollback. You can click
and drag the scrollbar to quickly navigate through the history.
can use either keyboard shortcuts or the mouse scroll wheel to do so. While
you are browsing the scrollback a :opt:`small indicator <scrollback_indicator_opacity>`
is displayed along the right edge of the window to show how far back you are.
However, |kitty| has an extra, neat feature. Sometimes you need to explore the scrollback
buffer in more detail, maybe search for some text or refer to it side-by-side
@@ -223,7 +290,7 @@ arbitrary, command running in a new :term:`window`, :term:`tab` or
Would open the scrollback buffer in a new :term:`window` when you press the
:kbd:`F1` key. See :sc:`show_scrollback <show_scrollback>` for details.
If you want to use it with an editor such as :program:`nvim` to get more powerful
If you want to use it with an editor such as :program:`vim` to get more powerful
features, see for example, `kitty-scrollback.nvim
<https://github.com/mikesmithgh/kitty-scrollback.nvim>`__ or `kitty-grab <https://github.com/yurikhan/kitty_grab>`__
or see more tips for using various editor programs, in :iss:`this thread <719>`.

View File

@@ -101,7 +101,7 @@ ASCII only text.
foot, iterm2 and Terminal.app are left out as they do not run under X11.
Alacritty+tmux is included just to show the effect of putting a terminal
multiplexer into the mix (halving throughput) and because alacritty isn't
multiplexer into the mix (halving throughput) and because alacritty isnt
remotely comparable to any of the other terminals feature wise without tmux.
.. note::

View File

@@ -166,7 +166,7 @@ Legacy xterm compatibility
----------------------------
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
aliases for the CSS based names defined in the :ref:`pointer_shape_names` table.

View File

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

View File

@@ -1,30 +0,0 @@
.. sidebar::
.. only:: not man
**Screenshots**
.. figure:: /screenshots/quake-macos.webp
:alt: Screenshot, showing the kitty floating quick access terminal above the background which is the program btop, running inside kitty, on macOS
:align: center
:width: 100%
macOS
.. figure:: /screenshots/quake-hypr.webp
:alt: Screenshot, showing the kitty floating quick access terminal above the background which is the program btop, running inside kitty, on Hyprland in Linux
:align: center
:width: 100%
Linux
.. figure:: /screenshots/panel.png
:alt: Screenshot, showing a sample panel
:align: center
:width: 100%
A sample panel on Linux
How the screenshots :ref:`were generated <quake_ss>`.

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
and Linux.
.. image:: https://repology.org/badge/tiny-repos/kitty-terminal.svg
:target: https://repology.org/project/kitty-terminal/versions
.. image:: https://repology.org/badge/tiny-repos/kitty.svg
:target: https://repology.org/project/kitty/versions
:alt: Number of repositories kitty is available in
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
--------------------------------
To control kitty from outside kitty, it is necessary to setup a socket to
communicate with kitty. First, start |kitty| as::
First, start |kitty| as::
kitty -o allow_remote_control=yes --listen-on unix:/tmp/mykitty
@@ -186,7 +185,7 @@ Now, using this password, you can, in scripts run the command::
Any script with access to the password can now change colors in kitty using
remote control, but only that and nothing else. You can even supply the
password via the :envvar:`KITTY_RC_PASSWORD` environment variable, or the
file :file:`~/.config/kitty/rc-pass` to avoid having to type it repeatedly.
file :file:`~/.config/kitty/rc-password` to avoid having to type it repeatedly.
See :option:`kitten @ --password-file` and :option:`kitten @ --password-env`.
The :opt:`remote_control_password` can be specified multiple times to create
@@ -262,12 +261,6 @@ as shown below:
return True
.. note::
The payloads for the different remote control commands are documented in the
:doc:`remote control protocol specification <rc_protocol>`.
.. _rc_mapping:
Mapping key presses to remote control commands

View File

@@ -1,8 +1,6 @@
# sphinx-inline-tabs breaks with sphinx >= 9
sphinx <= 8.2.3
sphinx
furo
sphinx-copybutton
sphinxext-opengraph
sphinx-inline-tabs
sphinx-autobuild
matplotlib

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 290 KiB

View File

@@ -1,375 +0,0 @@
.. _sessions:
Sessions
=============
kitty has robust support for sessions. A session is basically a simple text
file where you can define kitty windows, tabs and what programs to run in them
as well as how to layout the windows. kitty also supports actions to easily
:ac:`create and switch between existing sessions <goto_session>`, so that you
can move seamlessly from working on one project to another with a couple of keystrokes.
Let's see a quick example to get a feel of how easy it is to create sessions. First,
a session file to develop a project:
.. code-block:: session
# Set the layout for the current tab
layout tall
# Set the working directory for windows in the current tab
cd ~/path/to/myproject
# Create the "main" window and run an editor in it to edit the project files
launch --title "Edit My Project" /usr/bin/nvim
# Create a side window to run a shell to build or test project
launch --title "Build My Project"
# Create another side window to keep an eye on some useful log file
launch --title "Log for my project" /usr/bin/tail -f /path/to/project/log/file
Save this file as :file:`~/path/to/myproject/launch.kitty-session`. Now when
you want to work on the project, simply run:
.. code-block:: sh
kitty --session ~/path/to/myproject/launch.kitty-session
You can also set the session in :file:`kitty.conf` via :opt:`startup_session`.
Thus, it is very easy to create sessions and work on projects. To learn how to
create more complex sessions, see :ref:`complex_sessions`.
.. _goto_session:
Creating/Switching to sessions with a keypress
------------------------------------------------
If you like to manage multiple sessions within a single terminal and
easily swap between them, kitty has you covered. You can use the
:ac:`goto_session` action in kitty.conf, like this:
.. code-block:: conf
# Press F7 and then c to jump to the "cool" project
map f7>c goto_session ~/path/to/cool/cool.kitty-session
# Press F7 and then h to jump to the "hot" project
map f7>h goto_session ~/path/to/hot/hot.kitty-session
# Browse and select from the list of known projects defined via goto_session commands
map f7>/ goto_session
# Same as above, but the sessions are listed alphabetically instead of by most recent
map f7>/ goto_session --sort-by=alphabetical
# Browse session files inside a directory and pick one
map f7>p goto_session ~/.local/share/kitty/sessions
# Go to the previously active session (larger negative numbers jump further back in history)
map f7>- goto_session -1
In this manner you can define as many projects/sessions as you like and easily
switch between them with a keypress.
When a directory path is supplied to :ac:`goto_session`, kitty scans it for
files ending in ``.kitty-session``, ``.kitty_session`` or ``.session`` and
presents an interactive list. The ``--sort-by`` option controls the ordering of that list just like it does
for globally known sessions.
You can also close sessions using the :ac:`close_session` action, which closes
all windows in the session with a single keypress.
Displaying the currently active session name
----------------------------------------------
You can display the name of the currently active session file in the kitty tab
bar using :opt:`tab_title_template`. For example, using the value::
{session_name} {title}
will show you the name of the session file the current tab was loaded from, as
well as the normal tab title. Or alternatively, you can set the tab title
directly to a project name in the session file itself when creating the tab,
like this::
new_tab My Project Name
.. _complex_sessions:
More complex sessions
-------------------------
If you want to create more complex sessions, with sophisticated layouts, such
as :ref:`splits_layout`, the easiest way is to set up the state you want to
save manually by first starting kitty like this:
.. code-block:: sh
kitty -o 'map f1 save_as_session --use-foreground-process --relocatable'
Now create whatever splits and tabs you need and start whatever programs such
as editors, REPLs, debuggers, etc. you want to start in each of them. Once
kitty is the way you want it, press the :kbd:`F1` key, and you will be prompted
for a path at which to save the session file. Specify the path and the session
will be saved there with the exact setup you created. The saved file will even
be opened in your editor for you to review, automatically.
.. tip::
If you want session files to be saved to a specific directory regardless of
your current working directory, use the ``--base-dir`` option. For example::
map f7>s save_as_session --use-foreground-process --base-dir ~/.local/share/kitty/sessions
This is particularly useful when kitty is launched from system-wide shortcuts
where the working directory might not be your home directory. Note that
``--relocatable`` is typically not used with ``--base-dir``, since relocatable
is meant for session files that are co-located with their project directories.
If instead, you want to create these by hand, see the example below which shows
all the major keywords you can use in kitty session files:
.. code-block:: session
# Set the layout for the current tab
layout tall
# Set the working directory for windows in the current tab. Relative paths
# are resolved with respect to the location of this session file.
cd ~
# Create a window and run the specified command in it
launch zsh
# Create a window with some environment variables set and run vim in it
launch --env FOO=BAR vim
# Set the title for the next window
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
# The part after new_tab is the optional tab title which will be displayed in
# the tab bar, if omitted, the title of the active window will be used instead.
new_tab my tab
cd somewhere
# Set the layouts allowed in this tab
enabled_layouts tall,stack
# Set the current layout
layout stack
launch zsh
# Create a new OS window
# Any definitions specified before the first new_os_window will apply to first OS window.
new_os_window
# Set new window size to 80x24 cells
os_window_size 80c 24c
# Set the --title for the new OS window
os_window_title my fancy os window
# Set the --class for the new OS window
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
os_window_state normal
launch sh
# Resize the current window (see the resize_window action for details)
resize_window wider 2
# Make the current window the active (focused) window in its tab
focus
# Make the current OS Window the globally active window
focus_os_window
launch emacs
# Create another tab
new_tab logs
launch tail -f /var/log/syslog
# Focus the first tab (index 0) when the session loads
# You can also use a match expression like: focus_tab title:logs
focus_tab 0
# 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::
The :doc:`launch <launch>` command when used in a session file cannot create
new OS windows, or tabs.
.. note::
Environment variables of the form :code:`${NAME}` or :code:`$NAME` are
expanded in the session file, except in the *arguments* (not options) to the
launch command. For example:
.. code-block:: sh
launch --cwd=$THIS_IS_EXPANDED some-program $THIS_IS_NOT_EXPANDED
Making newly created windows join an existing session
---------------------------------------------------------
Normally, after activating a session, if you create new windows/tabs
they don't belong to the session. If you would prefer to have them belong
to the currently active session, you can use the :ac:`new_window_with_cwd`
and :ac:`new_tab_with_cwd` actions instead, like this::
map kitty_mod+enter new_window_with_cwd
map kitty_mod+t new_tab_with_cwd
map kitty_mod+n new_os_window_with_cwd
This will cause newly created windows and tabs to belong to the currently active
session, if any. Note that adding a window to a session in this way is
temporary, it does not edit the session file. If you wish to update the
session file of the currently active session, you can use the following
mapping for it::
map f5 save_as_session --relocatable --use-foreground-process --match=session:. .
The two can be combined, using the :ac:`combine` action.
For even more control of what session a window is added to use
the :doc:`launch <launch>` command with the :option:`launch --add-to-session`
flag.
Sessions with remote connections
-------------------------------------
If you use the :doc:`ssh kitten </kittens/ssh>` to connect to remote computers,
:ac:`save_as_session` is smart enough to save the ssh kitten invocation to your
session file, preserving the remote working directory and even the currently
running program on the remote host! Try it, run kitty with::
kitty -o 'map f1 save_as_session --use-foreground-process --relocatable' --session <(echo "layout vertical\nlaunch\nlaunch")
Now in both windows, run::
kitten ssh localhost
To connect them both to a remote computer (replace ``localhost`` with another
computer if you like). In one window change the directory to /tmp and in the
other start some program. Then press :kbd:`F1` to save the session file.
When you run the session file in another kitty instance you will see both
windows re-created, as expected with the correct working directories and
running programs.
Managing multi tab sessions in a single OS Window
----------------------------------------------------
The natural way to organise sessions in kitty is one per :term:`os_window`.
However, if you prefer to manage multiple sessions in a single OS Window, you
can configure the kitty tab bar to only show tabs that belong to the currently
active session. To do so, use :opt:`tab_bar_filter` in :file:`kitty.conf` set::
tab_bar_filter session:~ or session:^$
This will restrict the tab bar to only showing tabs from the currently active
session as well tabs that do not belong to any session. Furthermore, when you
are in a window or tab that does not belong to any session, the tab bar will
show the tabs from the most recent active session, to maintain context.
Keyword reference
---------------------
Below is the list of all supported keywords in session files along with
documentation for them.
``cd [path]``
Change the working directory for all windows in the current tab to
``path``. Relative paths are resolved with respect to the directory
containing the session file.
``focus``
Give keyboard focus to the window created by the previous launch command
``focus_matching_window``
Give keyboard focus to window that matches the specified expression. See
:ref:`search_syntax` for the syntax for matching expressions.
``focus_os_window``
Give keyboard focus to the current OS Window. This is guaranteed to work
only is some other OS Window in the current kitty process has focus,
otherwise the window manager might block changing focus to prevent *focus
stealing*.
``focus_tab [tab specifier]``
Set which tab should be active (focused) in the current OS Window. The tab
specifier can be either a plain number (treated as a 0-based index) or a
match expression. For example, ``focus_tab 0`` will focus the first tab,
``focus_tab 1`` the second tab, and ``focus_tab title:logs`` will focus the
tab whose title matches "logs". See :ref:`search_syntax` for the full syntax
of match expressions. This is useful for session files that create multiple
tabs and want to ensure a specific tab is active when the session is loaded.
``enabled_layouts comma separated list of layout names``
Set the layouts allowed in the current tab. Same syntax as
:opt:`enabled_layouts`.
``launch``
Create a new window running the specified command or the default shell if
no command is specified. See :doc:`launch` for details. Note that creating
tabs and OS Windows using launch is not supported in session files, use the
dedicated keywords for these.
``layout name``
Set the layout for the current tab to the specified layout, including any
specified options, see :doc:`layouts` for the available layouts and
options.
``new_os_window``
Create a new OS Window. Any OS window related keywords specified before the
first ``new_os_window`` will apply to the first OS Window.
``new_tab [tab title]``
Create a new tab with the specified title. If no title is specified, the
title behaves just as for a regular tab in kitty.
``os_window_title``
Set the title for the current OS Window. The OS Window will then always
have this title, it will not change based on the title of the currently active
window inside the OS Window.
``os_window_class``
Set the class part of WM_CLASS or Wayland Application Id for the current OS Window
``os_window_name``
Set the name part of WM_CLASS or Wayland Window tag for the current OS Window
``os_window_size``
Set the size of the current OS Window, can be specified in pixels or cells.
For example: 80c 24c is a window of width 80 cells by 24 cells.
``os_window_state``
Set the state of the current OS Window, can be: ``normal``, ``fullscreen``, ``maximized`` or ``minimized``
``resize_window``
Resize the current window. See the :ac:`resize_window` action for details.
For example: resize_window wider 2
``set_layout_state``
This keyword is only used in session files generated by the
:ac:`save_as_session` action, it's syntax is undocumented and for internal
use only.
``title``
Set the title for the next window. Deprecated, use ``launch --title``
instead.
.. _save_as_session:
The save_as_session action
------------------------------
This action can be mapped to a key press in :file:`kitty.conf`. It will save
the currently open OS Windows, tabs, windows, running programs, working
directories, etc. into a session file. It is a convenient way to
:ref:`complex_sessions`. The options this action takes are documented below.
.. include:: generated/save-as-session.rst

View File

@@ -421,27 +421,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
example: kitty, iTerm2, WezTerm, DomTerm
Just before starting to draw the PS1 prompt send the escape code:
.. code-block:: none
Just before starting to draw the PS1 prompt send the escape code::
<OSC>133;A<ST>
Just before starting to draw the PS2 prompt send the escape code:
.. code-block:: none
Just before starting to draw the PS2 prompt send the escape code::
<OSC>133;A;k=s<ST>
Just before running a command/program, send the escape code:
.. code-block:: none
Just before running a command/program, send the escape code::
<OSC>133;C<ST>
Optionally, when a command is finished its "exit status" can be reported as:
.. code-block:: none
Optionally, when a command is finished its "exit status" can be reported as::
<OSC>133;D;exit status as base 10 integer<ST>
@@ -451,34 +443,27 @@ full protocol, that also marks the command region, see `the iTerm2 docs
<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:
to control its behavior, separated by semi-colons. They are::
``redraw=0``
this tells kitty that the shell will not redraw the prompt on
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
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
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
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
kitty also optionally supports sending the cmdline going to be executed with ``<OSC>133;C`` as::
<OSC>133;C;cmdline=cmdline encoded by %q<ST>
or

View File

@@ -1,485 +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 code points 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 ``CR``
(carriage return) followed by ``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 the cursor
moved by two cells, then the width part is supported and if the third response has the
cursor moved by another two cells, then 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`. If serious issues
are brought to light in that discussion, there may be small changes to the
algorithm to address them. 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.
A terminal using this algorithm must decode the bytes they receive
into Unicode scalar values (i.e., code points except surrogates) using UTF-8.
When it encounters any UTF-8 ill-formed subsequences,
it must be replace each
`maximal subpart of the ill-formed subsequence <https://www.unicode.org/versions/Unicode16.0.0/core-spec/chapter-3/#G66453>`__
with a :code:`U+FFFD REPLACEMENT CHARACTER` (<28>).
For each decoded code point:
#. First check if the code point is an ASCII control code, and handle it
appropriately. ASCII control codes are the code points less than :code:`U+0032` and the
code point :code:`U+0127 DEL`. The code point :code:`U+0000 NUL` must be discarded.
#. Next, check if the code point is *invalid*, and if it is, discard it
and finish processing. Invalid code points are code points with Unicode category :code:`Cc or Cs`
and 66 additional code points: :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 code point,
which can be 0, 1, or 2 depending on the code point properties in
the Unicode standard.
#. If there is no previous cell and the code point's width is zero,
the code point is discarded and its processing 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 code point.
#. If there is no boundary, the current code point is added to the previous
cell and processing of the code point 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 code point is zero,
it is added to the previous cell and processing is finished.
#. The code point 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 code point.
It remains to specify how to calculate the width in cells of a code point.
To do this, code points 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 code points 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 code points marked :code:`W` or :code:`F` have
width two. All code points 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 codepoints in all
:code:`RGI_Emoji_Modifier_Sequence` and :code:`RGI_Emoji_Tag_Sequence` have width two.
All code points in :code:`RGI_Emoji_Flag_Sequence` have width two.
#. *Marks*: These are all zero width code points. They are code points with Unicode
categories whose first letter is :code:`M` or :code:`S`. Additionally,
code points with Unicode category: :code:`Cf`. Finally, they include
all modifier code points from :code:`RGI_Emoji_Modifier_Sequence` in the
*Wide emoji* rule above.
#. All remaining code points have a width of one cell.
.. _var_select:
Unicode variation selectors
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
There are two code points (:code:`U+FE0E` and :code:`U+FE0F`) that can actually
alter the width of the previous code point. When adding a code point 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 code point in the previous
cell is one of the ``Basic_Emoji`` code points 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 code point in the previous
cell is one of the ``Basic_Emoji`` code points 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,27 +0,0 @@
package kitty
import (
_ "embed"
"encoding/json"
"fmt"
)
var _ = fmt.Print
//go:embed logo/kitty.png
var KittyLogoAsPNGData []byte
//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

@@ -69,7 +69,7 @@ def parse_number(keymap: KeymapType) -> tuple[str, str]:
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]:
flag_fmt, flag_attrs = [], []
cv = {'flag': 'c', 'int': 'i', 'uint': 'I'}[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}'
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:
ans = [f'REPORT_VA_COMMAND("K s {{{fmt}}}", self->window_id, "{report_name}",\n']
if flag_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')
ans = [f'REPORT_VA_COMMAND("K s {{{fmt}}}", self->window_id, "{report_name}", ']
ans.append(',\n '.join((flag_attrs, uint_attrs, int_attrs)))
if payload_allowed:
if payload_is_base64:
ans.append('"", (char*)parser_buf, g.payload_sz')
else:
ans.append('"", (char*)parser_buf + payload_start, g.payload_sz')
ans.append(', "payload_sz", g.payload_sz, parser_buf, g.payload_sz')
ans.append(');')
return '\n'.join(ans)
@@ -110,63 +102,46 @@ def generate(
keymap: KeymapType,
command_class: str,
initial_key: str = 'a',
payload_allowed: bool = True,
payload_is_base64: bool = True,
start_parsing_at: int = 1,
field_sep: str = ',',
payload_allowed: bool = True
) -> str:
type_map = resolve_keys(keymap)
keys_enum = enum(keymap)
handle_key = parse_key(keymap)
flag_keys = parse_flag(keymap, type_map, command_class)
int_keys, uint_keys = parse_number(keymap)
report_cmd = cmd_for_report(report_name, keymap, type_map, payload_allowed, payload_is_base64)
extra_init = ''
report_cmd = cmd_for_report(report_name, keymap, type_map, payload_allowed)
if payload_allowed:
payload_after_value = "case ';': state = PAYLOAD; break;"
payload = ', PAYLOAD'
if payload_is_base64:
payload_case = f'''
case PAYLOAD: {{
sz = parser_buf_pos - pos;
payload_case = f'''
case PAYLOAD: {{
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);
if (!base64_decode8(parser_buf + pos, sz, parser_buf, &g.payload_sz)) {{
g.payload_sz = MAX(BUF_EXTRA, sz);
REPORT_ERROR("Failed to parse {command_class} command payload with error: \
invalid base64 data in chunk of size: %zu with output buffer size: %zu", sz, g.payload_sz); return; }}
pos = parser_buf_pos;
}} break;
'''
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)'
REPORT_ERROR("Failed to parse {command_class} command payload with error: \
invalid base64 data in chunk of size: %zu with output buffer size: %zu", sz, g.payload_sz); return; }}
pos = parser_buf_pos;
}}
break;
'''
callback = f'{callback_name}(self->screen, &g, parser_buf)'
else:
payload_after_value = payload = payload_case = ''
callback = f'{callback_name}(self->screen, &g)'
return f'''
#include "base64.h"
static inline void
{function_name}(PS *self, uint8_t *parser_buf, const size_t parser_buf_pos) {{
unsigned int pos = {start_parsing_at};
{extra_init}
unsigned int pos = 1;
enum PARSER_STATES {{ KEY, EQUAL, UINT, INT, FLAG, AFTER_VALUE {payload} }};
enum PARSER_STATES state = KEY, value_state = FLAG;
{command_class} g = {{0}};
static {command_class} g;
unsigned int i, code;
uint64_t lcode; int64_t accumulator;
bool is_negative; (void)is_negative;
bool is_negative;
memset(&g, 0, sizeof(g));
size_t sz;
{keys_enum}
enum KEYS key = '{initial_key}';
@@ -239,10 +214,10 @@ static inline void
case AFTER_VALUE:
switch (parser_buf[pos++]) {{
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]);
return;
case '{field_sep}':
case ',':
state = KEY;
break;
{payload_after_value}
@@ -281,7 +256,7 @@ def write_header(text: str, path: str) -> None:
subprocess.check_call(['clang-format', '-i', path])
def parsers() -> None:
def graphics_parser() -> None:
flag = frozenset
keymap: KeymapType = {
'a': ('action', flag('tTqpdfac')),
@@ -316,22 +291,10 @@ def parsers() -> None:
}
text = generate('parse_graphics_code', 'screen_handle_graphics_command', 'graphics_command', keymap, 'GraphicsCommand')
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:
parsers()
graphics_parser()
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

@@ -43,27 +43,17 @@ def main(args: list[str]=sys.argv) -> None:
from kitty.options.definition import definition
nullable_colors = []
all_colors = []
special_colors = []
for opt in definition.iter_all_options():
if callable(opt.parser_func):
match opt.parser_func.__name__:
case 'to_color':
all_colors.append(opt.name)
case 'macos_titlebar_color' | 'titlebar_color' | 'scrollbar_color':
all_colors.append(opt.name)
special_colors.append(opt.name)
case 'to_color_or_none' | 'cursor_text_color':
all_colors.append(opt.name)
nullable_colors.append(opt.name)
if opt.parser_func.__name__ in ('to_color_or_none', 'cursor_text_color'):
nullable_colors.append(opt.name)
all_colors.append(opt.name)
elif opt.parser_func.__name__ in ('to_color', 'titlebar_color', 'macos_titlebar_color'):
all_colors.append(opt.name)
patch_color_list('tools/cmd/at/set_colors.go', nullable_colors, 'NULLABLE')
patch_color_list('tools/themes/collection.go', all_colors, 'ALL')
nc = ',\n '.join(f'{x!r}' for x in nullable_colors)
sc = ',\n '.join(f'{x!r}' for x in special_colors)
write_output('kitty', definition,
f'\nnullable_colors = frozenset({{\n {nc}\n}})\n'
f'\nspecial_colors = frozenset({{\n {sc}\n}})\n'
)
nc = '\n '.join(f'{x!r}' for x in nullable_colors)
write_output('kitty', definition, f'\nnullable_colors = frozenset({{\n {nc}\n}})')
if __name__ == '__main__':

View File

@@ -118,7 +118,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('kitty/data-types.h', 'mouse shapes', '\n'.join(f' {x},' for x in enum_to_glfw_map))
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='',
)
patch_file('kitty/options/to-c.h', 'pointer shapes', '\n'.join(

View File

@@ -29,12 +29,14 @@ from kittens.tui.operations import Mode
from kittens.tui.spinners import spinners
from kitty.actions import get_all_actions
from kitty.cli import (
CompletionSpec,
GoOption,
go_options_for_seq,
parse_option_spec,
serialize_as_go_string,
)
from kitty.conf.generate import gen_go_code
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.key_encoding import config_mod_map
from kitty.key_names import character_key_name_aliases, functional_key_name_aliases
@@ -42,7 +44,6 @@ from kitty.options.types import Options
from kitty.rc.base import RemoteCommand, all_command_names, command_for_name
from kitty.remote_control import global_options_spec
from kitty.rgb import color_names
from kitty.simple_cli_definitions import CompletionSpec, parse_option_spec, serialize_as_go_string
if __name__ == '__main__' and not __package__:
import __main__
@@ -177,33 +178,6 @@ def stringify() -> None:
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',)
mb('kittens/choose_files', 'CombinedScore', 'score 16', 'length 16', 'index 32')
# }}}
# Completions {{{
@lru_cache
@@ -247,7 +221,7 @@ def clone_safe_launch_opts() -> Sequence[GoOption]:
ans = []
allowed = clone_safe_opts()
for o in go_options_for_seq(parse_option_spec(options_spec())[0]):
if o.obj_defn.name in allowed:
if o.obj_dict['name'] in allowed:
ans.append(o)
return tuple(ans)
@@ -260,9 +234,9 @@ def completion_for_launch_wrappers(*names: str) -> None:
def generate_completions_for_kitty() -> None:
print('package completion\n')
print('import "github.com/kovidgoyal/kitty/tools/cli"')
print('import "github.com/kovidgoyal/kitty/tools/cmd/tool"')
print('import "github.com/kovidgoyal/kitty/tools/cmd/at"')
print('import "kitty/tools/cli"')
print('import "kitty/tools/cmd/tool"')
print('import "kitty/tools/cmd/at"')
print('func kitty(root *cli.Command) {')
@@ -451,6 +425,15 @@ def go_code_for_remote_command(name: str, cmd: RemoteCommand, template: str) ->
# kittens {{{
@lru_cache
def wrapped_kittens() -> tuple[str, ...]:
with open('shell-integration/ssh/kitty') as f:
for line in f:
if line.startswith(' wrapped_kittens="'):
val = line.strip().partition('"')[2][:-1]
return tuple(sorted(filter(None, val.split())))
raise Exception('Failed to read wrapped kittens from kitty wrapper script')
def generate_conf_parser(kitten: str, defn: Definition) -> None:
with replace_if_needed(f'kittens/{kitten}/conf_generated.go'):
@@ -459,7 +442,7 @@ def generate_conf_parser(kitten: str, defn: Definition) -> None:
def generate_extra_cli_parser(name: str, spec: str) -> None:
print('import "github.com/kovidgoyal/kitty/tools/cli"')
print('import "kitty/tools/cli"')
go_opts = tuple(go_options_for_seq(parse_option_spec(spec)[0]))
print(f'type {name}_options struct ''{')
for opt in go_opts:
@@ -504,13 +487,10 @@ def kitten_clis() -> None:
with replace_if_needed(f'kittens/{kitten}/cli_generated.go'):
od = []
ser = []
kcd = kitten_cli_docs(kitten)
has_underscore = '_' in kitten
print(f'package {kitten}')
print('import "fmt"')
print('import "github.com/kovidgoyal/kitty/tools/cli"')
print('var _ = fmt.Sprintf')
print('import "kitty/tools/cli"')
print('func create_cmd(root *cli.Command, run_func func(*cli.Command, *Options, []string)(int, error)) {')
print('ans := root.AddSubCommand(&cli.Command{')
print(f'Name: "{kitten}",')
@@ -531,7 +511,6 @@ def kitten_clis() -> None:
for opt in gopts:
print(opt.as_option('ans'))
od.append(opt.struct_declaration())
ser.append('\n'.join(opt.as_string_for_commandline()))
if ac is not None:
print(''.join(ac.as_go_code('ans.ArgCompleter', ' = ')))
if not kcd:
@@ -544,14 +523,6 @@ def kitten_clis() -> None:
print('type Options struct {')
print('\n'.join(od))
print('}')
print('func (opts Options) AsCommandLine() (ans []string) {')
if ser:
print('\t sval := ""')
print('\t _ = sval')
for x in ser:
print('\t' + x)
print('return')
print('}')
# }}}
@@ -611,11 +582,9 @@ def load_ref_map() -> dict[str, dict[str, str]]:
def generate_constants() -> str:
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.fast_data_types import FILE_TRANSFER_CODE
from kitty.options.utils import allowed_shell_integration_values, url_style_map
from kitty.simple_cli_definitions import CONFIG_HELP
del sys.modules['kittens.hints.main']
del sys.modules['kittens.query_terminal.main']
ref_map = load_ref_map()
@@ -648,7 +617,6 @@ const HintsDefaultRegex = `{DEFAULT_REGEX}`
const DefaultTermName = `{Options.term}`
const DefaultUrlStyle = `{url_style}`
const DefaultUrlColor = `{Options.url_color.as_sharp}`
const ConfigHelp = "{serialize_as_go_string(CONFIG_HELP)}"
var Version VersionType = VersionType{{Major: {kc.version.major}, Minor: {kc.version.minor}, Patch: {kc.version.patch},}}
var DefaultPager []string = []string{{ {dp} }}
var FunctionalKeyNameAliases = map[string]string{serialize_go_dict(functional_key_name_aliases)}
@@ -658,10 +626,9 @@ var RefMap = map[string]string{serialize_go_dict(ref_map['ref'])}
var DocTitleMap = map[string]string{serialize_go_dict(ref_map['doc'])}
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 {{
Term, Shell_integration, Select_by_word_characters, Url_excluded_characters, Shell string
Wheel_scroll_multiplier float64
Wheel_scroll_multiplier int
Url_prefixes []string
}}{{
Term: "{Options.term}", Shell_integration: "{' '.join(Options.shell_integration)}", Url_prefixes: []string{{ {url_prefixes} }},
@@ -669,9 +636,6 @@ Select_by_word_characters: `{Options.select_by_word_characters}`, Wheel_scroll_m
Shell: "{Options.shell}", Url_excluded_characters: "{Options.url_excluded_characters}",
}}
const OptionNames = {option_names}
const DarkThemeFileName = "{ThemeFile.dark.value}"
const LightThemeFileName = "{ThemeFile.light.value}"
const NoPreferenceThemeFileName = "{ThemeFile.no_preference.value}"
''' # }}}
@@ -726,7 +690,7 @@ def update_at_commands() -> None:
odef = '\n'.join(opt_def)
code = f'''
package at
import "github.com/kovidgoyal/kitty/tools/cli"
import "kitty/tools/cli"
type rc_global_options struct {{
{sdef}
}}
@@ -754,7 +718,7 @@ def update_completion() -> None:
with replace_if_needed('tools/cmd/edit_in_kitty/launch_generated.go'):
print('package edit_in_kitty')
print('import "github.com/kovidgoyal/kitty/tools/cli"')
print('import "kitty/tools/cli"')
print('func AddCloneSafeOpts(cmd *cli.Command) {')
completion_for_launch_wrappers('cmd')
print(''.join(CompletionSpec.from_string('type:file mime:text/* group:"Text files"').as_go_code('cmd.ArgCompleter', ' = ')))
@@ -936,7 +900,6 @@ def main(args: list[str]=sys.argv) -> None:
update_at_commands()
kitten_clis()
stringify()
make_bitfields()
print(json.dumps(changed, indent=2))
stdout, stderr = simdgen_process.communicate()
if simdgen_process.wait() != 0:

11101
gen/nerd-fonts-glyphs.txt Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -10,8 +10,7 @@ import subprocess
cmdline = (
'glad --out-path {dest} --api gl:core=3.1 '
' --extensions GL_ARB_texture_storage,GL_ARB_copy_image,GL_ARB_multisample,GL_ARB_robustness,'
'GL_ARB_instanced_arrays,GL_KHR_debug,GL_ARB_framebuffer_sRGB,GL_EXT_framebuffer_sRGB '
' --extensions GL_ARB_texture_storage,GL_ARB_copy_image,GL_ARB_multisample,GL_ARB_robustness,GL_ARB_instanced_arrays,GL_KHR_debug '
'c --header-only --debug'
)

30
glfw/backend_utils.c vendored
View File

@@ -234,12 +234,6 @@ mark_wakep_fd_ready(int fd UNUSED, int events UNUSED, void *data) {
((EventLoopData*)(data))->wakeup_fd_ready = true;
}
static void
mark_key_repeat_fd_ready(int fd UNUSED, int events UNUSED, void *data) {
((EventLoopData*)(data))->key_repeat_fd_ready = true;
}
bool
initPollData(EventLoopData *eld, int display_fd) {
if (!addWatch(eld, "display", display_fd, POLLIN, 1, NULL, NULL)) return false;
@@ -252,20 +246,6 @@ initPollData(EventLoopData *eld, int display_fd) {
const int wakeup_fd = eld->wakeupFds[0];
#endif
if (!addWatch(eld, "wakeup", wakeup_fd, POLLIN, 1, mark_wakep_fd_ready, eld)) return false;
#ifdef HAS_TIMER_FD
eld->key_repeat_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
if (eld->key_repeat_fd < 0) return false;
const int key_repeat_fd = eld->key_repeat_fd;
#else
if (pipe2(eld->key_repeat_fds, O_CLOEXEC | O_NONBLOCK) != 0) return false;
const int key_repeat_fd = eld->key_repeat_fds[0];
#endif
#ifdef _GLFW_WAYLAND
if (!addWatch(eld, "key_repeat", key_repeat_fd, POLLIN, 1, mark_key_repeat_fd_ready, eld)) return false;
#else
(void)key_repeat_fd; (void)mark_key_repeat_fd_ready;
#endif
return true;
}
@@ -289,6 +269,7 @@ wakeupEventLoop(EventLoopData *eld) {
#endif
}
#ifndef HAS_EVENT_FD
static void
closeFds(int *fds, size_t count) {
while(count--) {
@@ -299,20 +280,15 @@ closeFds(int *fds, size_t count) {
fds++;
}
}
#endif
void
finalizePollData(EventLoopData *eld) {
(void)closeFds;
#ifdef HAS_EVENT_FD
close(eld->wakeupFd); eld->wakeupFd = -1;
#else
closeFds(eld->wakeupFds, arraysz(eld->wakeupFds));
#endif
#ifdef HAS_TIMER_FD
close(eld->key_repeat_fd); eld->key_repeat_fd = -1;
#else
closeFds(eld->key_repeat_fds, arraysz(eld->key_repeat_fds));
#endif
}
int
@@ -322,7 +298,7 @@ pollForEvents(EventLoopData *eld, monotonic_t timeout, watch_callback_func displ
EVDBG("pollForEvents final timeout: %.3f", monotonic_t_to_s_double(timeout));
int result;
monotonic_t end_time = monotonic() + timeout;
eld->wakeup_fd_ready = false; eld->key_repeat_fd_ready = false;
eld->wakeup_fd_ready = false;
while(1) {
if (timeout >= 0) {

11
glfw/backend_utils.h vendored
View File

@@ -36,10 +36,6 @@
#define HAS_EVENT_FD
#include <sys/eventfd.h>
#endif
#if __has_include(<sys/timerfd.h>)
#define HAS_TIMER_FD
#include <sys/timerfd.h>
#endif
#else
#define HAS_EVENT_FD
#include <sys/eventfd.h>
@@ -77,12 +73,7 @@ typedef struct {
#else
int wakeupFds[2];
#endif
#ifdef HAS_TIMER_FD
int key_repeat_fd;
#else
int key_repeat_fds[2];
#endif
bool wakeup_data_read, wakeup_fd_ready, key_repeat_fd_ready;
bool wakeup_data_read, wakeup_fd_ready;
nfds_t watches_count, timers_count;
Watch watches[32];
Timer timers[128];

View File

@@ -1,286 +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>
#include <os/lock.h>
#define DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL s_to_monotonic_t(30ll)
#define MAX_NUM_OF_DISPLAYS 256
typedef struct _GLFWDisplayLinkNS
{
CVDisplayLinkRef displayLink;
CGDirectDisplayID displayID;
monotonic_t lastRenderFrameRequestedAt, first_unserviced_render_frame_request_at;
bool pending_dispatch;
} _GLFWDisplayLinkNS;
static struct {
_GLFWDisplayLinkNS entries[MAX_NUM_OF_DISPLAYS];
os_unfair_lock locks[MAX_NUM_OF_DISPLAYS];
bool locks_initialized[MAX_NUM_OF_DISPLAYS];
size_t count;
} displayLinks = {0};
static inline size_t
index_for_entry(_GLFWDisplayLinkNS *entry) {
return (size_t)(entry - displayLinks.entries);
}
static inline os_unfair_lock*
lock_for_entry(_GLFWDisplayLinkNS *entry) {
return &displayLinks.locks[index_for_entry(entry)];
}
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++) {
_GLFWDisplayLinkNS *entry = &displayLinks.entries[i];
os_unfair_lock *lock = &displayLinks.locks[i];
os_unfair_lock_lock(lock);
CVDisplayLinkRef link = entry->displayLink;
entry->displayLink = NULL;
entry->displayID = (CGDirectDisplayID)0;
entry->lastRenderFrameRequestedAt = 0;
entry->first_unserviced_render_frame_request_at = 0;
entry->pending_dispatch = false;
os_unfair_lock_unlock(lock);
if (link) {
CVDisplayLinkStop(link);
CVDisplayLinkRelease(link);
}
}
displayLinks.count = 0;
}
static void _glfwDispatchRenderFrame(void *);
static CVReturn
displayLinkCallback(
CVDisplayLinkRef displayLink UNUSED,
const CVTimeStamp* now UNUSED, const CVTimeStamp* outputTime UNUSED,
CVOptionFlags flagsIn UNUSED, CVOptionFlags* flagsOut UNUSED, void* userInfo) {
_GLFWDisplayLinkNS *entry = (_GLFWDisplayLinkNS *)userInfo;
if (entry) {
os_unfair_lock *lock = lock_for_entry(entry);
os_unfair_lock_lock(lock);
const bool should_dispatch = entry->first_unserviced_render_frame_request_at &&
!entry->pending_dispatch;
CGDirectDisplayID displayID = entry->displayID;
if (should_dispatch) entry->pending_dispatch = true;
os_unfair_lock_unlock(lock);
if (should_dispatch) dispatch_async_f(dispatch_get_main_queue(), (void*)(uintptr_t)displayID, _glfwDispatchRenderFrame);
}
return kCVReturnSuccess;
}
static void
_glfw_create_cv_display_link(_GLFWDisplayLinkNS *entry) {
CVDisplayLinkCreateWithCGDisplay(entry->displayID, &entry->displayLink);
if (entry->displayLink) CVDisplayLinkSetOutputCallback(entry->displayLink, &displayLinkCallback, entry);
}
unsigned
_glfwCreateDisplayLink(CGDirectDisplayID displayID) {
for (unsigned i = 0; i < displayLinks.count; i++) {
os_unfair_lock *existing_lock = &displayLinks.locks[i];
os_unfair_lock_lock(existing_lock);
const bool already_created = displayLinks.entries[i].displayID == displayID;
os_unfair_lock_unlock(existing_lock);
if (already_created) return i;
}
if (displayLinks.count >= MAX_NUM_OF_DISPLAYS) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Too many monitors cannot create display link");
return displayLinks.count;
}
unsigned idx = displayLinks.count;
_GLFWDisplayLinkNS *entry = &displayLinks.entries[idx];
if (!displayLinks.locks_initialized[idx]) {
displayLinks.locks[idx] = OS_UNFAIR_LOCK_INIT;
displayLinks.locks_initialized[idx] = true;
}
os_unfair_lock *lock = &displayLinks.locks[idx];
os_unfair_lock_lock(lock);
memset(entry, 0, sizeof(entry[0]));
entry->displayID = displayID;
displayLinks.count++;
_glfw_create_cv_display_link(entry);
os_unfair_lock_unlock(lock);
return idx;
}
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];
os_unfair_lock *lock = &displayLinks.locks[i];
os_unfair_lock_lock(lock);
CVDisplayLinkRef link = dl->displayLink;
if (link) CVDisplayLinkRetain(link);
dl->lastRenderFrameRequestedAt = 0;
dl->first_unserviced_render_frame_request_at = 0;
dl->pending_dispatch = false;
os_unfair_lock_unlock(lock);
if (link) {
CVDisplayLinkStop(link);
CVDisplayLinkRelease(link);
}
}
}
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];
bool need_start = false, need_stop = false, need_recreate = false;
bool retain_link = false;
CVDisplayLinkRef link = NULL;
CVDisplayLinkRef new_link = NULL;
bool new_link_retained = false;
os_unfair_lock *lock = &displayLinks.locks[i];
os_unfair_lock_lock(lock);
link = dl->displayLink;
if (dl->displayID == displayID) {
found_display_link = true;
monotonic_t first_unserviced = dl->first_unserviced_render_frame_request_at;
dl->lastRenderFrameRequestedAt = now;
if (!first_unserviced) {
dl->first_unserviced_render_frame_request_at = now;
first_unserviced = now;
}
dl->pending_dispatch = false;
if (link) {
if (!CVDisplayLinkIsRunning(link)) {
need_start = true;
retain_link = true;
} else if (now - first_unserviced > s_to_monotonic_t(1ll)) {
need_recreate = true;
dl->first_unserviced_render_frame_request_at = now;
dl->displayLink = NULL;
}
}
} else if (link && dl->lastRenderFrameRequestedAt && now - dl->lastRenderFrameRequestedAt >= DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL) {
need_stop = true;
retain_link = true;
dl->lastRenderFrameRequestedAt = 0;
dl->first_unserviced_render_frame_request_at = 0;
dl->pending_dispatch = false;
}
if (retain_link && link) CVDisplayLinkRetain(link);
os_unfair_lock_unlock(lock);
if (need_recreate && link) {
CVDisplayLinkStop(link);
CVDisplayLinkRelease(link);
os_unfair_lock_lock(lock);
_glfw_create_cv_display_link(dl);
new_link = dl->displayLink;
if (new_link) {
CVDisplayLinkRetain(new_link);
new_link_retained = true;
}
dl->pending_dispatch = false;
os_unfair_lock_unlock(lock);
if (new_link) {
if (!CVDisplayLinkIsRunning(new_link)) CVDisplayLinkStart(new_link);
if (new_link_retained) CVDisplayLinkRelease(new_link);
}
_glfwInputError(GLFW_PLATFORM_ERROR,
"CVDisplayLink stuck possibly because of sleep/screensaver + Apple's incompetence, recreating.");
} else {
if (need_start && link) CVDisplayLinkStart(link);
else if (need_stop && link) CVDisplayLinkStop(link);
if (retain_link && link) CVDisplayLinkRelease(link);
}
}
if (!found_display_link) {
unsigned idx = _glfwCreateDisplayLink(displayID);
if (idx < displayLinks.count) {
dl = &displayLinks.entries[idx];
os_unfair_lock *lock = &displayLinks.locks[idx];
os_unfair_lock_lock(lock);
dl->lastRenderFrameRequestedAt = now;
dl->first_unserviced_render_frame_request_at = now;
dl->pending_dispatch = false;
CVDisplayLinkRef link = dl->displayLink;
if (link) CVDisplayLinkRetain(link);
os_unfair_lock_unlock(lock);
if (link) {
if (!CVDisplayLinkIsRunning(link)) CVDisplayLinkStart(link);
CVDisplayLinkRelease(link);
}
}
}
}
static void
_glfwDispatchRenderFrame(void *passed_in_data) {
CGDirectDisplayID displayID = (uintptr_t)passed_in_data;
_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];
bool need_stop = false;
CVDisplayLinkRef link = NULL;
os_unfair_lock *lock = &displayLinks.locks[i];
os_unfair_lock_lock(lock);
if (dl->displayID == displayID) {
dl->first_unserviced_render_frame_request_at = 0;
dl->pending_dispatch = false;
bool any_pending_request = false;
_GLFWwindow *window = _glfw.windowListHead;
while (window) {
if (window->ns.renderFrameRequested && displayID == displayIDForWindow(window)) {
any_pending_request = true;
break;
}
window = window->next;
}
link = dl->displayLink;
if (!any_pending_request && link && CVDisplayLinkIsRunning(link)) {
need_stop = true;
CVDisplayLinkRetain(link);
dl->lastRenderFrameRequestedAt = 0;
}
}
os_unfair_lock_unlock(lock);
if (need_stop && link) {
CVDisplayLinkStop(link);
CVDisplayLinkRelease(link);
}
}
}

View File

@@ -29,7 +29,6 @@
#include "internal.h"
#include "../kitty/monotonic.h"
#include <sys/param.h> // For MAXPATHLEN
#include <sys/sysctl.h>
#include <pthread.h>
// Needed for _NSGetProgname
@@ -298,19 +297,10 @@ static NSDictionary<NSString*,NSNumber*> *global_shortcuts = nil;
// Delegate for application related notifications {{{
@interface GLFWApplicationDelegate : NSObject <NSApplicationDelegate>
- (void)handleAppearanceChange;
@end
@implementation GLFWApplicationDelegate
- (void)applicationDidActivate:(NSNotification *)notification {
NSRunningApplication *app = notification.userInfo[NSWorkspaceApplicationKey];
if (app && app.processIdentifier != getpid()) {
_glfw.ns.previous_front_most_application = app.processIdentifier;
debug_rendering("Front most application changed to: %s pid: %d\n", app.bundleIdentifier.UTF8String, app.processIdentifier)
}
}
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
(void)sender;
@@ -368,7 +358,8 @@ static GLFWapplicationwillfinishlaunchingfun finish_launching_callback = NULL;
else
createMenuBar();
}
if (finish_launching_callback) finish_launching_callback(false);
if (finish_launching_callback)
finish_launching_callback();
}
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
@@ -417,76 +408,19 @@ static GLFWapplicationwillfinishlaunchingfun finish_launching_callback = NULL;
}
}
static void *AppearanceObservationContext = &AppearanceObservationContext;
static NSDate *application_finished_launching_at = nil;
- (void)applicationDidFinishLaunching:(NSNotification *)notification
{
(void)notification;
[[NSApplication sharedApplication] addObserver:self
forKeyPath:@"effectiveAppearance" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial
context:AppearanceObservationContext];
if (finish_launching_callback) finish_launching_callback(true);
[NSApp stop:nil];
CGDisplayRegisterReconfigurationCallback(display_reconfigured, NULL);
_glfwCocoaPostEmptyEvent();
application_finished_launching_at = [NSDate date];
}
GLFWAPI GLFWColorScheme glfwGetCurrentSystemColorTheme(bool query_if_unintialized) {
(void)query_if_unintialized;
int theme_type = GLFW_COLOR_SCHEME_NO_PREFERENCE;
NSAppearance *changedAppearance = NSApp.effectiveAppearance;
NSAppearanceName newAppearance = [changedAppearance bestMatchFromAppearancesWithNames:@[NSAppearanceNameAqua, NSAppearanceNameDarkAqua]];
if([newAppearance isEqualToString:NSAppearanceNameDarkAqua]){
theme_type = GLFW_COLOR_SCHEME_DARK;
} else {
theme_type = GLFW_COLOR_SCHEME_LIGHT;
}
return theme_type;
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey, id> *)change
context:(void *)context {
if (context == AppearanceObservationContext) {
if ([keyPath isEqualToString:@"effectiveAppearance"]) {
// The initial call (from NSKeyValueObservingOptionInitial) might happen on a background thread.
// Dispatch to the main thread to be safe, especially if updating UI.
__block __typeof__(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf handleAppearanceChange];
});
}
} else {
// If the context doesn't match, pass the notification to the superclass.
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void)handleAppearanceChange {
static GLFWColorScheme previously_reported_appearance = GLFW_COLOR_SCHEME_NO_PREFERENCE;
GLFWColorScheme new_appearance = glfwGetCurrentSystemColorTheme(true);
if (new_appearance != previously_reported_appearance) {
previously_reported_appearance = new_appearance;
_glfwInputColorScheme(new_appearance, false);
}
}
- (void)applicationWillTerminate:(NSNotification *)aNotification
{
(void)aNotification;
CGDisplayRemoveReconfigurationCallback(display_reconfigured, NULL);
@try {
[[NSApplication sharedApplication] removeObserver:self
forKeyPath:@"effectiveAppearance"
context:AppearanceObservationContext];
} @catch (NSException * __unused exception) {
// Ignore exceptions, which can happen if the observer was never added.
}
}
- (void)applicationDidHide:(NSNotification *)notification
@@ -504,6 +438,7 @@ GLFWAPI GLFWColorScheme glfwGetCurrentSystemColorTheme(bool query_if_unintialize
@interface GLFWApplication : NSApplication
- (void)tick_callback;
- (void)render_frame_received:(id)displayIDAsID;
@end
@implementation GLFWApplication
@@ -512,6 +447,11 @@ GLFWAPI GLFWColorScheme glfwGetCurrentSystemColorTheme(bool query_if_unintialize
_glfwDispatchTickCallback();
}
- (void)render_frame_received:(id)displayIDAsID
{
CGDirectDisplayID displayID = [(NSNumber*)displayIDAsID unsignedIntValue];
_glfwDispatchRenderFrame(displayID);
}
@end
@@ -851,11 +791,10 @@ GLFWAPI GLFWapplicationwillfinishlaunchingfun glfwSetApplicationWillFinishLaunch
return previous;
}
int _glfwPlatformInit(bool *supports_window_occlusion)
int _glfwPlatformInit(void)
{
@autoreleasepool {
*supports_window_occlusion = true;
_glfw.ns.helper = [[GLFWHelper alloc] init];
[NSThread detachNewThreadSelector:@selector(doNothing:)
@@ -876,11 +815,6 @@ int _glfwPlatformInit(bool *supports_window_occlusion)
}
[NSApp setDelegate:_glfw.ns.delegate];
[[[NSWorkspace sharedWorkspace] notificationCenter]
addObserver:_glfw.ns.delegate
selector:@selector(applicationDidActivate:)
name:NSWorkspaceDidActivateApplicationNotification
object:nil];
static struct {
unsigned short virtual_key_code;
NSEventModifierFlags input_source_switch_modifiers;
@@ -893,7 +827,7 @@ int _glfwPlatformInit(bool *supports_window_occlusion)
{
debug_key("---------------- key down -------------------\n");
debug_key("%s\n", [[event description] UTF8String]);
if (!_glfw.ignoreOSKeyboardProcessing && !_glfw.keyboard_grabbed) {
if (!_glfw.ignoreOSKeyboardProcessing) {
// first check if there is a global menu bar shortcut
if ([[NSApp mainMenu] performKeyEquivalent:event]) {
debug_key("keyDown triggered global menu bar action ignoring\n");
@@ -974,16 +908,13 @@ int _glfwPlatformInit(bool *supports_window_occlusion)
if (_glfw.hints.init.ns.chdir)
changeToResourcesDirectory();
[[NSUserDefaults standardUserDefaults] registerDefaults:@{
NSDictionary* defaults = @{
// Press and Hold prevents some keys from emitting repeated characters
@"ApplePressAndHoldEnabled": @NO,
// Dont generate openFile events from command line arguments
@"NSTreatUnknownArgumentsAsOpen": @"NO",
// This Tahoe nonsense causes slowdowns in some situations, see for example:
// https://issues.chromium.org/issues/452372350 it doesnt affect
// autofill via Edit->Autofill
@"NSAutoFillHeuristicControllerEnabled" : @NO,
}];
};
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
NSUserDefaults *apple_settings = [[NSUserDefaults alloc] initWithSuiteName:@"com.apple.symbolichotkeys"];
[apple_settings addObserver:_glfw.ns.helper
@@ -1012,47 +943,11 @@ int _glfwPlatformInit(bool *supports_window_occlusion)
} // autoreleasepool
}
static NSDate*
get_process_start_time(pid_t pid) {
struct kinfo_proc kp;
size_t len = sizeof(kp);
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid };
if (sysctl(mib, 4, &kp, &len, NULL, 0) == 0 && len == sizeof(kp)) {
struct timeval start_tv = kp.kp_proc.p_starttime;
time_t start_sec = start_tv.tv_sec;
suseconds_t start_usec = start_tv.tv_usec;
NSTimeInterval t = (NSTimeInterval)start_sec + ((NSTimeInterval)start_usec / 1000000.0);
return [NSDate dateWithTimeIntervalSince1970:t];
}
return nil;
}
void _glfwPlatformTerminate(void)
{
@autoreleasepool {
// Kill the AutoFill helper process that macOS Tahoe starts and fails to
// shutdown on application exit, see https://github.com/kovidgoyal/kitty/issues/9299
// Only kill helpers that were launched within a few seconds of this process to
// avoid killing helpers from other processes. This is obviously not robust
// but since Apple cant design its way out of a paper bag, it's the best we
// can do.
if (application_finished_launching_at != nil) {
for (NSRunningApplication *app in [[NSWorkspace sharedWorkspace] runningApplications]) {
if ([app.bundleIdentifier isEqualToString:@"com.apple.SafariPlatformSupport.Helper"] &&
[[app.localizedName lowercaseString] containsString:@"autofill (kitty)"]) {
NSDate *st = get_process_start_time(app.processIdentifier);
if (st != nil) {
NSTimeInterval timeDifference = [application_finished_launching_at timeIntervalSinceDate:st];
[st release];
if (fabs(timeDifference) <= 5) [app forceTerminate];
}
}
}
[application_finished_launching_at release]; application_finished_launching_at = nil;
}
_glfwClearDisplayLinks();
if (_glfw.ns.inputSource)
@@ -1248,4 +1143,3 @@ void _glfwPlatformUpdateTimer(unsigned long long timer_id, monotonic_t interval,
}
void _glfwPlatformInputColorScheme(GLFWColorScheme appearance UNUSED) { }
bool _glfwPlatformGrabKeyboard(bool grab UNUSED) { return true; /* directly uses _glfw.keyboard_grabbed */ }

View File

@@ -32,10 +32,10 @@
#include <stdlib.h>
#include <limits.h>
#include <math.h>
#include <assert.h>
#include <IOKit/graphics/IOGraphicsLib.h>
#include <CoreVideo/CVBase.h>
#include <CoreVideo/CVDisplayLink.h>
#include <ApplicationServices/ApplicationServices.h>
@@ -323,7 +323,54 @@ static double getFallbackRefreshRate(CGDirectDisplayID displayID)
////// 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
//
void _glfwPollMonitorsNS(void)
{
uint32_t displayCount;
@@ -379,7 +426,7 @@ void _glfwPollMonitorsNS(void)
{
disconnected[j]->ns.displayID = displays[i];
disconnected[j]->ns.screen = screen;
_glfwCreateDisplayLink(displays[i]);
_glfw_create_display_link(displays[i]);
disconnected[j] = NULL;
break;
}
@@ -400,7 +447,7 @@ void _glfwPollMonitorsNS(void)
monitor->ns.displayID = displays[i];
monitor->ns.unitNumber = unitNumber;
monitor->ns.screen = screen;
_glfwCreateDisplayLink(monitor->ns.displayID);
_glfw_create_display_link(monitor->ns.displayID);
free(name);
@@ -636,8 +683,6 @@ void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
GLFWAPI CGDirectDisplayID glfwGetCocoaMonitor(GLFWmonitor* handle)
{
_GLFWmonitor* monitor = (_GLFWmonitor*) handle;
assert(monitor != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(kCGNullDirectDisplay);
return monitor->ns.displayID;
}

35
glfw/cocoa_platform.h vendored
View File

@@ -30,8 +30,10 @@
#include <Carbon/Carbon.h>
#if defined(__OBJC__)
#import <Cocoa/Cocoa.h>
#import <CoreVideo/CoreVideo.h>
#else
typedef void* id;
typedef void* CVDisplayLinkRef;
#endif
// NOTE: Many Cocoa enum values have been renamed and we need to build across
@@ -67,7 +69,7 @@ typedef void* id;
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int, unsigned long);
typedef bool (* GLFWapplicationshouldhandlereopenfun)(int);
typedef bool (* GLFWhandleurlopen)(const char*);
typedef void (* GLFWapplicationwillfinishlaunchingfun)(bool);
typedef void (* GLFWapplicationwillfinishlaunchingfun)(void);
typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
typedef void (* GLFWcocoarenderframefun)(GLFWwindow*);
@@ -138,7 +140,6 @@ typedef struct _GLFWwindowNS
int fbWidth, fbHeight;
float xscale, yscale;
int blur_radius;
bool live_resize_in_progress;
// The total sum of the distances the cursor has been warped
// since the last cursor motion event was processed
@@ -151,13 +152,6 @@ typedef struct _GLFWwindowNS
GLFWcocoatogglefullscreenfun toggleFullscreenCallback;
// Dead key state
UInt32 deadKeyState;
// Layer shell windows
struct {
bool is_active;
GLFWLayerShellConfig config;
} layer_shell;
// Whether a render frame has been requested for this window
bool renderFrameRequested;
GLFWcocoarenderframefun renderFrameCallback;
@@ -166,6 +160,13 @@ typedef struct _GLFWwindowNS
GLFWcocoarenderframefun resizeCallback;
} _GLFWwindowNS;
typedef struct _GLFWDisplayLinkNS
{
CVDisplayLinkRef displayLink;
CGDirectDisplayID displayID;
monotonic_t lastRenderFrameRequestedAt, first_unserviced_render_frame_request_at;
} _GLFWDisplayLinkNS;
// Cocoa-specific global data
//
typedef struct _GLFWlibraryNS
@@ -189,7 +190,6 @@ typedef struct _GLFWlibraryNS
double restoreCursorPosX, restoreCursorPosY;
// The window whose disabled cursor mode is active
_GLFWwindow* disabledCursorWindow;
pid_t previous_front_most_application;
struct {
CFBundleRef bundle;
@@ -199,6 +199,10 @@ typedef struct _GLFWlibraryNS
CFStringRef kPropertyUnicodeKeyLayoutData;
} tis;
struct {
_GLFWDisplayLinkNS entries[256];
size_t count;
} displayLinks;
// the callback to handle url open events
GLFWhandleurlopen url_open_callback;
@@ -240,15 +244,12 @@ float _glfwTransformYNS(float y);
void* _glfwLoadLocalVulkanLoaderNS(void);
// display links
void _glfwClearDisplayLinks(void);
void _glfwRestartDisplayLinks(void);
unsigned _glfwCreateDisplayLink(CGDirectDisplayID);
void _glfwRequestRenderFrame(_GLFWwindow *w);
// event loop
void _glfwDispatchTickCallback(void);
void _glfwDispatchRenderFrame(CGDirectDisplayID);
void _glfwShutdownCVDisplayLink(unsigned long long, 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);

View File

@@ -35,7 +35,6 @@
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#include <float.h>
#include <string.h>
#include <assert.h>
#define debug debug_rendering
@@ -307,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
requestRenderFrame(_GLFWwindow *w, GLFWcocoarenderframefun callback) {
if (!callback) {
@@ -316,7 +337,46 @@ requestRenderFrame(_GLFWwindow *w, GLFWcocoarenderframefun callback) {
}
w->ns.renderFrameCallback = callback;
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
@@ -521,7 +581,6 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
@interface GLFWWindowDelegate : NSObject
{
_GLFWwindow* window;
NSArray<NSDictionary *> *_lastScreenStates;
}
- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow;
@@ -534,31 +593,15 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow
{
self = [super init];
if (self != nil) {
if (self != nil)
window = initWindow;
_lastScreenStates = [self captureScreenStates];
window->ns.live_resize_in_progress = false;
}
return self;
}
- (NSArray<NSDictionary *> *)captureScreenStates {
NSMutableArray *states = [NSMutableArray array];
for (NSScreen *screen in [NSScreen screens]) {
// Use the screen's deviceDescription, which contains a stable ID.
[states addObject:screen.deviceDescription];
}
return [states copy];
}
- (void)cleanup {
[_lastScreenStates release]; _lastScreenStates = nil;
}
- (BOOL)windowShouldClose:(id)sender
{
(void)sender;
if (window == nil) return YES;
_glfwInputWindowCloseRequest(window);
return NO;
}
@@ -566,21 +609,6 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
- (void)windowDidResize:(NSNotification *)notification
{
(void)notification;
NSArray<NSDictionary *> *currentScreenStates = [self captureScreenStates];
const bool is_screen_change = ![_lastScreenStates isEqualToArray:currentScreenStates];
NSWindowStyleMask sm = [window->ns.object styleMask];
const bool is_fullscreen = (sm & NSWindowStyleMaskFullScreen) != 0;
NSRect frame = [window->ns.object frame];
debug_rendering(
"windowDidResize() called, is_screen_change: %d is_fullscreen: %d live_resize_in_progress: %d frame: %.1fx%.1f@(%.1f, %.1f)\n",
is_screen_change, is_fullscreen, window->ns.live_resize_in_progress, frame.size.width, frame.size.height, frame.origin.x, frame.origin.y);
if (is_screen_change) {
// This resize likely happened because a screen was added, removed, or changed resolution.
[_lastScreenStates release];
_lastScreenStates = [currentScreenStates retain];
}
[currentScreenStates release];
if (window->context.client != GLFW_NO_API)
[window->context.nsgl.object update];
@@ -612,11 +640,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
window->ns.height = (int)contentRect.size.height;
_glfwInputWindowSize(window, (int)contentRect.size.width, (int)contentRect.size.height);
}
// Because of a bug in macOS Tahoe we cannot redraw the window in response
// to a resize event that was caused by a screen change as the OpenGL
// context is not ready yet. See: https://github.com/kovidgoyal/kitty/issues/8983
if (window->ns.resizeCallback && !is_screen_change && !is_fullscreen && window->ns.live_resize_in_progress)
window->ns.resizeCallback((GLFWwindow*)window);
if (window->ns.resizeCallback) window->ns.resizeCallback((GLFWwindow*)window);
}
- (void)windowDidMove:(NSNotification *)notification
@@ -787,21 +811,9 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
markedRect = NSMakeRect(0.0, 0.0, 0.0, 0.0);
input_source_at_last_key_event = nil;
in_key_handler = 0;
self.identifier = @"kitty-content-view";
[self updateTrackingAreas];
// Register for file promises in addition to regular files (macOS 10.12+)
if (@available(macOS 10.12, *)) {
NSMutableArray *types = [NSMutableArray arrayWithObjects:
NSPasteboardTypeFileURL,
NSPasteboardTypeString,
nil];
// Add file promise types
[types addObjectsFromArray:[NSFilePromiseReceiver readableDraggedTypes]];
[self registerForDraggedTypes:types];
} else {
[self registerForDraggedTypes:@[NSPasteboardTypeFileURL, NSPasteboardTypeString]];
}
[self registerForDraggedTypes:@[NSPasteboardTypeFileURL, NSPasteboardTypeString]];
}
return self;
@@ -843,14 +855,12 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
- (void) viewWillStartLiveResize
{
if (!window) return;
window->ns.live_resize_in_progress = true;
_glfwInputLiveResize(window, true);
}
- (void)viewDidEndLiveResize
{
if (!window) return;
window->ns.live_resize_in_progress = false;
_glfwInputLiveResize(window, false);
}
@@ -991,7 +1001,6 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
(void)event;
if (!window) return;
_glfwInputCursorEnter(window, false);
[[NSCursor arrowCursor] set];
}
- (void)mouseEntered:(NSEvent *)event
@@ -999,7 +1008,16 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
(void)event;
if (!window) return;
_glfwInputCursorEnter(window, true);
updateCursorImage(window);
}
- (void)viewDidChangeEffectiveAppearance
{
static GLFWColorScheme appearance = GLFW_COLOR_SCHEME_NO_PREFERENCE;
GLFWColorScheme new_appearance = glfwGetCurrentSystemColorTheme();
if (new_appearance != appearance) {
appearance = new_appearance;
_glfwInputColorScheme(appearance);
}
}
- (void)viewDidChangeBackingProperties
@@ -1671,21 +1689,13 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
if (self != nil) {
glfw_window = initWindow;
self.tabbingMode = NSWindowTabbingModeDisallowed;
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(screenParametersDidChange:) name:NSApplicationDidChangeScreenParametersNotification object:nil];
}
return self;
}
- (void)screenParametersDidChange:(NSNotification *)notification {
if (!glfw_window || !glfw_window->ns.layer_shell.is_active) return;
_glfwPlatformSetLayerShellConfig(glfw_window, NULL);
}
- (void) removeGLFWWindow
{
glfw_window = NULL;
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (BOOL)validateMenuItem:(NSMenuItem *)item {
@@ -1701,23 +1711,13 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
- (BOOL)canBecomeKeyWindow
{
if (!glfw_window) return NO;
if (glfw_window->ns.layer_shell.is_active) {
if (glfw_window->ns.layer_shell.config.type == GLFW_LAYER_SHELL_BACKGROUND) return NO;
switch(glfw_window->ns.layer_shell.config.focus_policy) {
case GLFW_FOCUS_NOT_ALLOWED: return NO;
case GLFW_FOCUS_EXCLUSIVE: return YES;
case GLFW_FOCUS_ON_DEMAND: return YES;
}
}
// Required for NSWindowStyleMaskBorderless windows
// Also miniaturized windows should not become key
return !_glfwPlatformWindowIconified(glfw_window);
return YES;
}
- (BOOL)canBecomeMainWindow
{
return !glfw_window->ns.layer_shell.is_active || glfw_window->ns.layer_shell.config.type != GLFW_LAYER_SHELL_BACKGROUND;
return YES;
}
static void
@@ -1878,12 +1878,12 @@ static bool createNativeWindow(_GLFWwindow* window,
////// GLFW platform API //////
//////////////////////////////////////////////////////////////////////////
int _glfwPlatformCreateWindow(_GLFWwindow* window, const _GLFWwndconfig* wndconfig, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig, const GLFWLayerShellConfig *lsc) {
int _glfwPlatformCreateWindow(_GLFWwindow* window,
const _GLFWwndconfig* wndconfig,
const _GLFWctxconfig* ctxconfig,
const _GLFWfbconfig* fbconfig)
{
window->ns.deadKeyState = 0;
if (lsc) {
window->ns.layer_shell.is_active = true;
window->ns.layer_shell.config = *lsc;
} else window->ns.layer_shell.is_active = false;
if (!_glfw.ns.finishedLaunching)
{
[NSApp run];
@@ -1941,11 +1941,10 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window, const _GLFWwndconfig* wndconf
void _glfwPlatformDestroyWindow(_GLFWwindow* window)
{
GLFWWindow *w = window->ns.object;
if (_glfw.ns.disabledCursorWindow == window)
_glfw.ns.disabledCursorWindow = NULL;
[w orderOut:nil];
[window->ns.object orderOut:nil];
if (window->monitor)
releaseMonitor(window);
@@ -1953,8 +1952,7 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
if (window->context.destroy)
window->context.destroy(window);
[w setDelegate:nil];
[window->ns.delegate cleanup];
[window->ns.object setDelegate:nil];
[window->ns.delegate release];
window->ns.delegate = nil;
@@ -1962,183 +1960,11 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
[window->ns.view release];
window->ns.view = nil;
[w removeGLFWWindow];
// Workaround for macOS Tahoe where if the frame is not set to zero size
// even after NSWindow::close the window remains on screen as an invisible
// rectangle that intercepts mouse events and takes up space in mission
// control. Sigh.
NSRect frame = w.frame; frame.size.width = 0; frame.size.height = 0;
[w setFrame:frame display:NO];
[w setHasShadow:NO];
[w close]; // sends a release to NSWindow so we dont release it
[window->ns.object removeGLFWWindow];
[window->ns.object close];
window->ns.object = nil;
}
static NSScreen*
screen_for_window_center(_GLFWwindow *window) {
NSRect windowFrame = [window->ns.object frame];
NSPoint windowCenter = NSMakePoint(NSMidX(windowFrame), NSMidY(windowFrame));
for (NSScreen *screen in [NSScreen screens]) {
if (NSPointInRect(windowCenter, [screen frame])) {
return screen;
}
}
return NSScreen.mainScreen;
}
static NSScreen*
active_screen(void) {
NSPoint mouseLocation = [NSEvent mouseLocation];
NSArray<NSScreen *> *screens = [NSScreen screens];
for (NSScreen *screen in screens) {
if (NSPointInRect(mouseLocation, [screen frame])) {
return screen;
}
}
// As a fallback, return the main screen
return [NSScreen mainScreen];
}
static bool
is_same_screen(NSScreen *screenA, NSScreen * screenB) {
if (screenA == screenB) return true;
NSDictionary<NSDeviceDescriptionKey, id> *deviceDescriptionA = [screenA deviceDescription];
NSDictionary<NSDeviceDescriptionKey, id> *deviceDescriptionB = [screenB deviceDescription];
NSNumber *screenNumberA = deviceDescriptionA[@"NSScreenNumber"];
NSNumber *screenNumberB = deviceDescriptionB[@"NSScreenNumber"];
return [screenNumberA isEqualToNumber:screenNumberB];
}
static void
move_window_to_screen(_GLFWwindow *window, NSScreen *target) {
NSRect screenFrame = [target visibleFrame];
NSRect windowFrame = [window->ns.object frame];
CGFloat newX = NSMidX(screenFrame) - (windowFrame.size.width / 2.0);
CGFloat newY = NSMidY(screenFrame) - (windowFrame.size.height / 2.0);
NSRect newWindowFrame = NSMakeRect(newX, newY, windowFrame.size.width, windowFrame.size.height);
[window->ns.object setFrame:newWindowFrame display:NO animate:NO];
if (window->ns.layer_shell.is_active) _glfwPlatformSetLayerShellConfig(window, NULL);
}
const GLFWLayerShellConfig*
_glfwPlatformGetLayerShellConfig(_GLFWwindow *window) {
return &window->ns.layer_shell.config;
}
static NSScreen*
screen_for_name(const char *name) {
int count = 0;
GLFWmonitor **monitors = glfwGetMonitors(&count);
for (int i = 0; i < count; i++) {
const char *q = glfwGetMonitorName(monitors[i]);
if (q && strcmp(q, name) == 0) return ((_GLFWmonitor*)monitors[i])->ns.screen;
}
return NULL;
}
bool
_glfwPlatformSetLayerShellConfig(_GLFWwindow* window, const GLFWLayerShellConfig *value) {
#define config window->ns.layer_shell.config
#define nswindow window->ns.object
window->resizable = false;
if (value) config = *value;
const bool is_transparent = _glfwPlatformFramebufferTransparent(window);
int background_blur = config.related.background_blur;
if (!is_transparent || config.related.background_opacity >= 1.f) { background_blur = 0; }
[nswindow setBackgroundColor:[NSColor clearColor]];
_glfwPlatformSetWindowBlur(window, background_blur);
window->ns.titlebar_hidden = true;
window->decorated = false;
[nswindow setTitlebarAppearsTransparent:false];
[nswindow setHasShadow:false];
[nswindow setTitleVisibility:NSWindowTitleHidden];
NSColorSpace *cs = nil;
switch (config.related.color_space) {
case SRGB_COLORSPACE: cs = [NSColorSpace sRGBColorSpace]; break;
case DISPLAY_P3_COLORSPACE: cs = [NSColorSpace displayP3ColorSpace]; break;
case DEFAULT_COLORSPACE: cs = nil; break; // using deviceRGBColorSpace causes a hang when transitioning to fullscreen
}
[nswindow setColorSpace:cs];
[[nswindow standardWindowButton: NSWindowCloseButton] setHidden:true];
[[nswindow standardWindowButton: NSWindowMiniaturizeButton] setHidden:true];
[[nswindow standardWindowButton: NSWindowZoomButton] setHidden:true];
[nswindow setStyleMask:NSWindowStyleMaskBorderless];
// HACK: Changing the style mask can cause the first responder to be cleared
[nswindow makeFirstResponder:window->ns.view];
NSScreen *screen = screen_for_window_center(window);
if (config.output_name[0]) {
NSScreen *q = screen_for_name(config.output_name);
if (q) screen = q;
}
unsigned cell_width, cell_height; double left_edge_spacing, top_edge_spacing, right_edge_spacing, bottom_edge_spacing;
float xscale = (float)config.expected.xscale, yscale = (float)config.expected.yscale;
_glfwPlatformGetWindowContentScale(window, &xscale, &yscale);
config.size_callback((GLFWwindow*)window, xscale, yscale, &cell_width, &cell_height, &left_edge_spacing, &top_edge_spacing, &right_edge_spacing, &bottom_edge_spacing);
double spacing_x = left_edge_spacing + right_edge_spacing;
double spacing_y = top_edge_spacing + bottom_edge_spacing;
const unsigned xsz = config.x_size_in_pixels ? (unsigned)(config.x_size_in_pixels * xscale) : (cell_width * config.x_size_in_cells);
const unsigned ysz = config.y_size_in_pixels ? (unsigned)(config.y_size_in_pixels * yscale) : (cell_height * config.y_size_in_cells);
CGFloat dock_height = NSMinY(screen.visibleFrame) - NSMinY(screen.frame);
CGFloat menubar_height = NSHeight(screen.frame) - NSHeight(screen.visibleFrame) - dock_height;
CGFloat x = NSMinX(screen.visibleFrame), y = NSMinY(screen.visibleFrame) - 1, width = NSWidth(screen.visibleFrame), height = NSHeight(screen.visibleFrame) + 2;
if (config.type == GLFW_LAYER_SHELL_BACKGROUND || config.edge == GLFW_EDGE_CENTER) {
x = NSMinX(screen.frame); height = NSHeight(screen.frame) - menubar_height + 1; y = NSMinY(screen.frame); width = NSWidth(screen.frame);
}
// Screen co-ordinate system is with origin in lower left and y increasing upwards and x increasing rightwards
// NSLog(@"frame: %@ visibleFrame: %@\n", NSStringFromRect(screen.frame), NSStringFromRect(screen.visibleFrame));
NSWindowLevel level = NSPopUpMenuWindowLevel - 1; // so that popup menus from globalmenubar function
NSWindowAnimationBehavior animation_behavior = NSWindowAnimationBehaviorUtilityWindow;
switch (config.type) {
case GLFW_LAYER_SHELL_BACKGROUND:
animation_behavior = NSWindowAnimationBehaviorNone;
// See: https://stackoverflow.com/questions/4982584/how-do-i-draw-the-desktop-on-mac-os-x/4982619#4982619
level = kCGDesktopWindowLevel;
break;
case GLFW_LAYER_SHELL_OVERLAY: case GLFW_LAYER_SHELL_NONE: break;
case GLFW_LAYER_SHELL_PANEL: level = NSNormalWindowLevel - 1; break;
case GLFW_LAYER_SHELL_TOP: level--; break;
}
if (config.type != GLFW_LAYER_SHELL_BACKGROUND && config.edge != GLFW_EDGE_CENTER) {
double panel_height = spacing_y + ysz / yscale, panel_width = spacing_x + xsz / xscale;
switch (config.edge) {
case GLFW_EDGE_BOTTOM: height = panel_height; break;
case GLFW_EDGE_TOP:
y += height - panel_height + 1.;
height = panel_height;
break;
case GLFW_EDGE_LEFT: width = panel_width; break;
case GLFW_EDGE_RIGHT:
x += width - panel_width + 1.;
width = panel_width;
break;
case GLFW_EDGE_CENTER_SIZED:
x += (width - panel_width) / 2;
y += (height - panel_height) / 2;
width = panel_width; height = panel_height;
break;
default: // top left
y += height - panel_height + 1.;
height = panel_height; width = panel_width;
break;
}
if (width < 1.) width = NSWidth(screen.visibleFrame);
if (height < 1.) height = NSWidth(screen.visibleFrame);
}
if (config.edge != GLFW_EDGE_CENTER_SIZED) {
x += config.requested_left_margin; width -= config.requested_left_margin + config.requested_right_margin;
y += config.requested_bottom_margin; height -= config.requested_top_margin + config.requested_bottom_margin;
}
[nswindow setAnimationBehavior:animation_behavior];
[nswindow setLevel:level];
[nswindow setCollectionBehavior: (NSWindowCollectionBehaviorCanJoinAllSpaces | NSWindowCollectionBehaviorStationary | NSWindowCollectionBehaviorIgnoresCycle)];
[nswindow setFrame:NSMakeRect(x, y, width, height) display:YES];
return true;
#undef config
#undef nswindow
}
void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title)
{
if (!title) return;
@@ -2187,7 +2013,6 @@ void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height)
void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height)
{
if (window->ns.layer_shell.is_active) return;
if (window->monitor)
{
if (window->monitor->window == window)
@@ -2306,62 +2131,14 @@ void _glfwPlatformMaximizeWindow(_GLFWwindow* window)
}
}
void _glfwPlatformShowWindow(_GLFWwindow* window, bool move_to_active_screen)
void _glfwPlatformShowWindow(_GLFWwindow* window)
{
const bool is_background = window->ns.layer_shell.is_active && window->ns.layer_shell.config.type == GLFW_LAYER_SHELL_BACKGROUND;
NSWindow *nw = window->ns.object;
if (move_to_active_screen) {
NSScreen *current_screen = screen_for_window_center(window);
NSScreen *target_screen = active_screen();
if (!is_same_screen(current_screen, target_screen)) {
debug_rendering("Moving OS window %llu to active screen\n", window->id);
move_window_to_screen(window, target_screen);
}
}
if (is_background) {
[nw orderBack:nil];
} else {
// Cocoa has a bug where when showing a hidden window after
// fullscreening an application, the window does not get added
// to the current space even though it has NSWindowCollectionBehaviorCanJoinAllSpaces
// probably because it wasnt added to the temp space used for
// fullscreen. So to work around that, we change the collection
// behavior temporarily to NSWindowCollectionBehaviorMoveToActiveSpace
// and then change it back asynchronously.
// See https://github.com/kovidgoyal/kitty/issues/8740
NSWindowCollectionBehavior old = nw.collectionBehavior;
nw.collectionBehavior = (old & !NSWindowCollectionBehaviorCanJoinAllSpaces) | NSWindowCollectionBehaviorMoveToActiveSpace;
[nw orderFront:nil];
__block __typeof__(nw) weakSelf = nw;
dispatch_async(dispatch_get_main_queue(), ^{
weakSelf.collectionBehavior = old;
});
}
[window->ns.object orderFront:nil];
}
void _glfwPlatformHideWindow(_GLFWwindow* window)
{
[window->ns.object orderOut:nil];
pid_t prev_app_pid = _glfw.ns.previous_front_most_application; _glfw.ns.previous_front_most_application = 0;
NSRunningApplication *app;
if (window->ns.layer_shell.is_active && prev_app_pid > 0 && (app = [NSRunningApplication runningApplicationWithProcessIdentifier:prev_app_pid])) {
unsigned num_visible = 0;
for (_GLFWwindow *w = _glfw.windowListHead; w; w = w->next) {
if (_glfwPlatformWindowVisible(w)) num_visible++;
}
if (!num_visible) {
// yieldActivationToApplication was introduced in macOS 14 (Sonoma)
SEL selector = NSSelectorFromString(@"yieldActivationToApplication:");
if ([NSApp respondsToSelector:selector]) {
[NSApp performSelector:selector withObject:app];
[app activateWithOptions:0];
} else {
#define NSApplicationActivateIgnoringOtherApps 2
[app activateWithOptions:NSApplicationActivateIgnoringOtherApps];
#undef NSApplicationActivateIgnoringOtherApps
}
}
}
}
void _glfwPlatformRequestWindowAttention(_GLFWwindow* window UNUSED)
@@ -2377,16 +2154,12 @@ int _glfwPlatformWindowBell(_GLFWwindow* window UNUSED)
void _glfwPlatformFocusWindow(_GLFWwindow* window)
{
if (_glfwPlatformWindowIconified(window)) {
// miniaturized windows return false in canBecomeKeyWindow therefore
// unminiaturize first
[window->ns.object deminiaturize:nil];
}
if ([window->ns.object canBecomeKeyWindow]) {
// Make us the active application
[NSApp activateIgnoringOtherApps:YES];
[window->ns.object makeKeyAndOrderFront:nil];
}
// Make us the active application
// HACK: This is here to prevent applications using only hidden windows from
// being activated, but should probably not be done every time any
// window is shown
[NSApp activateIgnoringOtherApps:YES];
[window->ns.object makeKeyAndOrderFront:nil];
}
void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
@@ -2564,6 +2337,24 @@ bool _glfwPlatformRawMouseMotionSupported(void)
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)
{
const NSRect contentRect = [window->ns.view frame];
@@ -3238,8 +3029,6 @@ _glfwPlatformSetWindowBlur(_GLFWwindow *window, int radius) {
GLFWAPI id glfwGetCocoaWindow(GLFWwindow* handle)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(nil);
return window->ns.object;
}
@@ -3278,102 +3067,22 @@ GLFWAPI GLFWcocoarenderframefun glfwCocoaSetWindowResizeCallback(GLFWwindow *w,
return current;
}
@implementation NSView (FindByIdentifier)
- (NSArray<NSView *> *)viewsWithIdentifier:(NSUserInterfaceItemIdentifier)identifier {
NSMutableArray<NSView *> *result = [NSMutableArray array];
if ([self.identifier isEqual:identifier]) {
[result addObject:self];
}
for (NSView *sub in self.subviews) {
[result addObjectsFromArray:[sub viewsWithIdentifier:identifier]];
}
return result;
}
@end
static
void clear_title_bar_background_views(NSWindow *window) {
#define tag @"kitty-for-transparent-titlebar"
NSView *contentView = window.contentView, *titlebarContainer = contentView ? contentView.superview : nil;
if (titlebarContainer) {
for (NSView *subview in [titlebarContainer viewsWithIdentifier:tag]) [subview removeFromSuperview];
}
}
static void
set_title_bar_background(NSWindow *window, NSColor *backgroundColor) {
// add an extra view that just renders the background color under the transparent titlebar
NSView *contentView = window.contentView, *titlebarContainer = contentView ? contentView.superview : nil;
if (!titlebarContainer) return;
for (NSView *subview in [titlebarContainer viewsWithIdentifier:tag]) [subview removeFromSuperview];
if (!backgroundColor) return;
NSButton *b = [window standardWindowButton:NSWindowCloseButton];
if (b) {
NSView *titlebarView = b.superview;
NSView *bgView = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, titlebarView.bounds.size.width, titlebarView.bounds.size.height)];
bgView.translatesAutoresizingMaskIntoConstraints = NO;
bgView.wantsLayer = YES;
bgView.layer.backgroundColor = backgroundColor.CGColor;
bgView.identifier = tag;
[titlebarView addSubview:bgView positioned:NSWindowBelow relativeTo:titlebarView.subviews[0]];
[NSLayoutConstraint activateConstraints:@[
// Pin to the top of the content view.
[bgView.topAnchor constraintEqualToAnchor:titlebarView.topAnchor],
// Pin to the leading edge of the content view.
[bgView.leadingAnchor constraintEqualToAnchor:titlebarView.leadingAnchor],
// Pin to the trailing edge of the content view.
[bgView.trailingAnchor constraintEqualToAnchor:titlebarView.trailingAnchor],
// Give it a fixed height
[bgView.bottomAnchor constraintEqualToAnchor:titlebarView.bottomAnchor]
]];
[bgView release];
return;
}
NSView *bgView = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, titlebarContainer.bounds.size.width, 32)];
bgView.translatesAutoresizingMaskIntoConstraints = NO;
bgView.wantsLayer = YES;
bgView.layer.backgroundColor = backgroundColor.CGColor;
bgView.identifier = tag;
// position the background view above the content view but below the titlebar view
[titlebarContainer addSubview:bgView positioned:NSWindowAbove relativeTo:contentView];
// for (NSView *subview in titlebarContainer.subviews) NSLog(@"sv: %@", subview.identifier);
[NSLayoutConstraint activateConstraints:@[
// Pin to the top of the content view.
[bgView.topAnchor constraintEqualToAnchor:titlebarContainer.topAnchor],
// Pin to the leading edge of the content view.
[bgView.leadingAnchor constraintEqualToAnchor:titlebarContainer.leadingAnchor],
// Pin to the trailing edge of the content view.
[bgView.trailingAnchor constraintEqualToAnchor:titlebarContainer.trailingAnchor],
// Give it a fixed height
[bgView.bottomAnchor constraintEqualToAnchor:contentView.topAnchor]
]];
[bgView release];
#undef tag
}
GLFWAPI void glfwCocoaSetWindowChrome(GLFWwindow *w, unsigned int color, bool use_system_color, unsigned int system_color, int background_blur, unsigned int hide_window_decorations, bool show_text_in_titlebar, int color_space, float background_opacity, bool resizable) { @autoreleasepool {
_GLFWwindow* window = (_GLFWwindow*)w;
if (window->ns.layer_shell.is_active) return;
GLFWWindow *nsw = window->ns.object;
const bool is_transparent = _glfwPlatformFramebufferTransparent(window);
const bool is_transparent = ![window->ns.object isOpaque];
if (!is_transparent) { background_opacity = 1.0; background_blur = 0; }
NSColor *window_background = [NSColor windowBackgroundColor];
if (background_opacity < 1.0) {
// use a clear color (fully transparent) so that the final color is just the color from the surface.
// prevent blurring of shadows at window corners with desktop background by setting a low alpha background
window_background = background_blur > 0 ? [NSColor colorWithWhite: 0 alpha: 0.001f] : [NSColor clearColor];
}
NSColor *background = nil;
NSAppearance *appearance = nil;
bool titlebar_transparent = false;
NSColor *titlebar_color = nil;
const NSWindowStyleMask current_style_mask = [nsw styleMask];
const NSWindowStyleMask current_style_mask = [window->ns.object styleMask];
const bool in_fullscreen = ((current_style_mask & NSWindowStyleMaskFullScreen) != 0) || window->ns.in_traditional_fullscreen;
NSAppearance *light_appearance = is_transparent ? [NSAppearance appearanceNamed:NSAppearanceNameVibrantLight] : [NSAppearance appearanceNamed:NSAppearanceNameAqua];
NSAppearance *dark_appearance = is_transparent ? [NSAppearance appearanceNamed:NSAppearanceNameVibrantDark] : [NSAppearance appearanceNamed:NSAppearanceNameDarkAqua];
if (use_system_color) {
if (use_system_color || background_opacity < 1.0) {
if (is_transparent) {
// prevent blurring of shadows at window corners with desktop background by setting a low alpha background
background = background_blur > 0 ? [NSColor colorWithWhite: 0 alpha: 0.001f] : [NSColor clearColor];
} else background = [NSColor windowBackgroundColor];
switch (system_color) {
case 1:
appearance = light_appearance; break;
@@ -3381,16 +3090,17 @@ GLFWAPI void glfwCocoaSetWindowChrome(GLFWwindow *w, unsigned int color, bool us
appearance = dark_appearance; break;
}
} else {
// set a background color and make the title bar transparent so the background color is visible
double red = ((color >> 16) & 0xFF) / 255.0;
double green = ((color >> 8) & 0xFF) / 255.0;
double blue = (color & 0xFF) / 255.0;
double luma = 0.2126 * red + 0.7152 * green + 0.0722 * blue;
background = [NSColor colorWithSRGBRed:red green:green blue:blue alpha:1.f];
appearance = luma < 0.5 ? dark_appearance : light_appearance;
titlebar_color = [NSColor colorWithSRGBRed:red green:green blue:blue alpha:background_opacity];
titlebar_transparent = true;
}
[nsw setBackgroundColor:window_background];
[nsw setAppearance:appearance];
[window->ns.object setBackgroundColor:background];
[window->ns.object setAppearance:appearance];
_glfwPlatformSetWindowBlur(window, background_blur);
bool has_shadow = false;
const char *decorations_desc = "full";
@@ -3422,9 +3132,9 @@ GLFWAPI void glfwCocoaSetWindowChrome(GLFWwindow *w, unsigned int color, bool us
// https://github.com/kovidgoyal/kitty/issues/6439
if (is_transparent) has_shadow = false;
bool hide_titlebar_buttons = !in_fullscreen && window->ns.titlebar_hidden;
[nsw setTitlebarAppearsTransparent:titlebar_transparent];
[nsw setHasShadow:has_shadow];
[nsw setTitleVisibility:(show_text_in_titlebar) ? NSWindowTitleVisible : NSWindowTitleHidden];
[window->ns.object setTitlebarAppearsTransparent:titlebar_transparent];
[window->ns.object setHasShadow:has_shadow];
[window->ns.object setTitleVisibility:(show_text_in_titlebar) ? NSWindowTitleVisible : NSWindowTitleHidden];
NSColorSpace *cs = nil;
switch (color_space) {
case SRGB_COLORSPACE: cs = [NSColorSpace sRGBColorSpace]; break;
@@ -3435,35 +3145,44 @@ GLFWAPI void glfwCocoaSetWindowChrome(GLFWwindow *w, unsigned int color, bool us
debug(
"Window Chrome state:\n\tbackground: %s\n\tappearance: %s color_space: %s\n\t"
"blur: %d has_shadow: %d resizable: %d decorations: %s (%d)\n\t"
"titlebar_transparent: %d titlebar_color: %s title_visibility: %d hidden: %d buttons_hidden: %d"
"titlebar: transparent: %d title_visibility: %d hidden: %d buttons_hidden: %d"
"\n",
window_background ? [window_background.description UTF8String] : "<nil>",
background ? [background.description UTF8String] : "<nil>",
appearance ? [appearance.name UTF8String] : "<nil>",
cs ? (cs.localizedName ? [cs.localizedName UTF8String] : [cs.description UTF8String]) : "<nil>",
background_blur, has_shadow, resizable, decorations_desc, window->decorated, titlebar_transparent,
titlebar_color ? [titlebar_color.description UTF8String] : "<nil>",
show_text_in_titlebar, window->ns.titlebar_hidden, hide_titlebar_buttons
);
[nsw setColorSpace:cs];
[[nsw standardWindowButton: NSWindowCloseButton] setHidden:hide_titlebar_buttons];
[[nsw standardWindowButton: NSWindowMiniaturizeButton] setHidden:hide_titlebar_buttons];
[[nsw standardWindowButton: NSWindowZoomButton] setHidden:hide_titlebar_buttons];
[window->ns.object setColorSpace:cs];
[[window->ns.object standardWindowButton: NSWindowCloseButton] setHidden:hide_titlebar_buttons];
[[window->ns.object standardWindowButton: NSWindowMiniaturizeButton] setHidden:hide_titlebar_buttons];
[[window->ns.object standardWindowButton: NSWindowZoomButton] setHidden:hide_titlebar_buttons];
// Apple throws a hissy fit if one attempts to clear the value of NSWindowStyleMaskFullScreen outside of a full screen transition
// event. See https://github.com/kovidgoyal/kitty/issues/7106
NSWindowStyleMask fsmask = current_style_mask & NSWindowStyleMaskFullScreen;
window->ns.pre_full_screen_style_mask = getStyleMask(window);
if (in_fullscreen && window->ns.in_traditional_fullscreen) {
[nsw setStyleMask:NSWindowStyleMaskBorderless];
[window->ns.object setStyleMask:NSWindowStyleMaskBorderless];
} else {
[nsw setStyleMask:window->ns.pre_full_screen_style_mask | fsmask];
[window->ns.object setStyleMask:window->ns.pre_full_screen_style_mask | fsmask];
}
if (!window->ns.titlebar_hidden && window->decorated && titlebar_color != nil && titlebar_transparent) {
set_title_bar_background(nsw, titlebar_color);
} else clear_title_bar_background_views(nsw);
// HACK: Changing the style mask can cause the first responder to be cleared
[nsw makeFirstResponder:window->ns.view];
[window->ns.object makeFirstResponder:window->ns.view];
}}
GLFWAPI GLFWColorScheme glfwGetCurrentSystemColorTheme(void) {
int theme_type = 0;
NSAppearance *changedAppearance = NSApp.effectiveAppearance;
NSAppearanceName newAppearance = [changedAppearance bestMatchFromAppearancesWithNames:@[NSAppearanceNameAqua, NSAppearanceNameDarkAqua]];
if([newAppearance isEqualToString:NSAppearanceNameDarkAqua]){
theme_type = 1;
} else {
theme_type = 2;
}
return theme_type;
}
GLFWAPI uint32_t
glfwGetCocoaKeyEquivalent(uint32_t glfw_key, int glfw_mods, int *cocoa_mods) {
*cocoa_mods = 0;
@@ -3480,37 +3199,6 @@ glfwGetCocoaKeyEquivalent(uint32_t glfw_key, int glfw_mods, int *cocoa_mods) {
return _glfwPlatformGetNativeKeyForKey(glfw_key);
}
GLFWAPI bool glfwIsLayerShellSupported(void) { return true; }
GLFWAPI void
glfwCocoaCycleThroughOSWindows(bool backwards) {
NSArray *allWindows = [NSApp windows];
if (allWindows.count < 2) return;
NSMutableArray<NSWindow *> *filteredWindows = [NSMutableArray array];
for (NSWindow *window in allWindows) {
NSRect windowFrame = [window frame];
// Exclude zero size windows which are likely zombie windows from the Tahoe bug
// if ([obj isMemberOfClass:[MyClass class]]) {
if (
windowFrame.size.width > 0 && windowFrame.size.height > 0 && \
!window.isMiniaturized && window.isVisible && \
[window isMemberOfClass:[GLFWWindow class]]
) [filteredWindows addObject:window];
}
if (filteredWindows.count < 2) return;
NSWindow *keyWindow = [NSApp keyWindow];
NSUInteger index = [filteredWindows indexOfObject:keyWindow];
NSUInteger nextIndex = 0;
if (index != NSNotFound) {
if (backwards) {
nextIndex = (index == 0) ? [filteredWindows count] - 1 : index - 1;
} else nextIndex = (index + 1) % filteredWindows.count;
}
NSWindow *nextWindow = filteredWindows[nextIndex];
[nextWindow makeKeyAndOrderFront:nil];
}
//////////////////////////////////////////////////////////////////////////
////// GLFW internal API //////

40
glfw/dbus_glfw.c vendored
View File

@@ -214,6 +214,13 @@ typedef struct {
void *user_data;
} MethodResponse;
static const char*
format_message_error(DBusError *err) {
static char buf[1024];
snprintf(buf, sizeof(buf), "[%s] %s", err->name ? err->name : "", err->message);
return buf;
}
static void
method_reply_received(DBusPendingCall *pending, void *user_data) {
MethodResponse *res = (MethodResponse*)user_data;
@@ -221,27 +228,18 @@ method_reply_received(DBusPendingCall *pending, void *user_data) {
if (msg) {
DBusError err;
dbus_error_init(&err);
if (dbus_set_error_from_message(&err, msg)) res->callback(NULL, &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);
}
}
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;
#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) {
DBusPendingCall *pending = NULL;
if (block) {
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, 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)) {
if (dbus_connection_send_with_reply(conn, msg, &pending, timeout)) {
MethodResponse *res = malloc(sizeof(MethodResponse));
if (!res) return false;
res->callback = callback;
@@ -263,7 +261,7 @@ call_method_with_msg(DBusConnection *conn, DBusMessage *msg, int timeout, dbus_p
}
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;
RAII_MSG(msg, dbus_message_new_method_call(node, path, interface, method));
if (!msg) return false;
@@ -271,7 +269,7 @@ call_method(DBusConnection *conn, const char *node, const char *path, const char
int firstarg = va_arg(ap, int);
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 {
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to call DBUS method: %s on node: %s and interface: %s could not add arguments", method, node, interface);
}
@@ -284,17 +282,7 @@ glfw_dbus_call_method_with_reply(DBusConnection *conn, const char *node, const c
bool retval;
va_list ap;
va_start(ap, user_data);
retval = call_method(conn, node, path, interface, method, timeout, callback, user_data, false, 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);
retval = call_method(conn, node, path, interface, method, timeout, callback, user_data, ap);
va_end(ap);
return retval;
}
@@ -304,7 +292,7 @@ glfw_dbus_call_method_no_reply(DBusConnection *conn, const char *node, const cha
bool retval;
va_list ap;
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);
return retval;
}

6
glfw/dbus_glfw.h vendored
View File

@@ -33,7 +33,7 @@
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 DBusError *err, void* data);
typedef void(*dbus_pending_callback)(DBusMessage *msg, const char* err, void* data);
typedef struct {
EventLoopData* eld;
@@ -45,13 +45,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);
void glfw_dbus_close_connection(DBusConnection *conn);
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
glfw_dbus_call_method_no_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...);
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, ...);
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_session_bus_dispatch(void);
bool glfw_dbus_get_args(DBusMessage *msg, const char *failmsg, ...);

32
glfw/egl_context.c vendored
View File

@@ -223,7 +223,6 @@ static int extensionSupportedEGL(const char* extension)
static GLFWglproc getProcAddressEGL(const char* procname)
{
_GLFWwindow* window = _glfwPlatformGetTls(&_glfw.contextSlot);
assert(window != NULL);
if (window->context.egl.client)
{
@@ -388,7 +387,6 @@ bool _glfwInitEGL(void)
}
_glfw.egl.platform = _glfwPlatformGetEGLPlatform(&attribs);
_glfw.egl.display = EGL_NO_DISPLAY;
if (_glfw.egl.platform)
{
_glfw.egl.display =
@@ -396,18 +394,16 @@ bool _glfwInitEGL(void)
_glfwPlatformGetEGLNativeDisplay(),
attribs);
}
else
_glfw.egl.display = eglGetDisplay(_glfwPlatformGetEGLNativeDisplay());
free(attribs);
if (_glfw.egl.display == EGL_NO_DISPLAY)
_glfw.egl.display = eglGetDisplay(_glfwPlatformGetEGLNativeDisplay());
EGLint egl_err;
if (_glfw.egl.display == EGL_NO_DISPLAY && (egl_err = eglGetError()) != EGL_SUCCESS)
{
_glfwInputError(GLFW_API_UNAVAILABLE,
"EGL: Failed to get EGL display: %s",
getEGLErrorString(egl_err));
getEGLErrorString(eglGetError()));
_glfwTerminateEGL();
return false;
@@ -416,8 +412,8 @@ bool _glfwInitEGL(void)
if (!eglInitialize(_glfw.egl.display, &_glfw.egl.major, &_glfw.egl.minor))
{
_glfwInputError(GLFW_API_UNAVAILABLE,
"EGL: Failed to initialize EGL: %s display_present: %d egl_platform_present: %d",
getEGLErrorString(eglGetError()), _glfw.egl.display != EGL_NO_DISPLAY, _glfw.egl.platform != 0);
"EGL: Failed to initialize EGL: %s",
getEGLErrorString(eglGetError()));
_glfwTerminateEGL();
return false;
@@ -546,18 +542,18 @@ bool _glfwCreateContextEGL(_GLFWwindow* window,
flags |= EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR;
}
if (ctxconfig->major != 1 || ctxconfig->minor != 0)
{
setAttrib(EGL_CONTEXT_MAJOR_VERSION_KHR, ctxconfig->major);
setAttrib(EGL_CONTEXT_MINOR_VERSION_KHR, ctxconfig->minor);
}
if (ctxconfig->noerror)
{
if (_glfw.egl.KHR_create_context_no_error)
setAttrib(EGL_CONTEXT_OPENGL_NO_ERROR_KHR, true);
}
if (ctxconfig->major != 1 || ctxconfig->minor != 0)
{
setAttrib(EGL_CONTEXT_MAJOR_VERSION_KHR, ctxconfig->major);
setAttrib(EGL_CONTEXT_MINOR_VERSION_KHR, ctxconfig->minor);
}
if (mask)
setAttrib(EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, mask);
@@ -793,8 +789,6 @@ GLFWAPI EGLDisplay glfwGetEGLDisplay(void)
GLFWAPI EGLContext glfwGetEGLContext(GLFWwindow* handle)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(EGL_NO_CONTEXT);
if (window->context.client == GLFW_NO_API)
@@ -809,8 +803,6 @@ GLFWAPI EGLContext glfwGetEGLContext(GLFWwindow* handle)
GLFWAPI EGLSurface glfwGetEGLSurface(GLFWwindow* handle)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(EGL_NO_SURFACE);
if (window->context.client == GLFW_NO_API)

View File

@@ -313,7 +313,6 @@ def generate_wrappers(glfw_header: str) -> None:
void* glfwGetX11Display(void)
unsigned long glfwGetX11Window(GLFWwindow* window)
void glfwSetPrimarySelectionString(GLFWwindow* window, const char* string)
void glfwCocoaCycleThroughOSWindows(bool backwards)
void glfwCocoaSetWindowChrome(GLFWwindow* window, unsigned int color, bool use_system_color, unsigned int system_color,\
int background_blur, unsigned int hide_window_decorations, bool show_text_in_titlebar, int color_space, float background_opacity, bool resizable)
const char* glfwGetPrimarySelectionString(GLFWwindow* window, void)
@@ -324,12 +323,13 @@ def generate_wrappers(glfw_header: str) -> None:
void glfwWaylandRunWithActivationToken(GLFWwindow *handle, GLFWactivationcallback cb, void *cb_data)
bool glfwWaylandSetTitlebarColor(GLFWwindow *handle, uint32_t color, bool use_system_color)
void glfwWaylandRedrawCSDWindowTitle(GLFWwindow *handle)
bool glfwWaylandIsWindowFullyCreated(GLFWwindow *handle)
bool glfwWaylandBeep(GLFWwindow *handle)
void glfwWaylandSetupLayerShellForNextWindow(const GLFWLayerShellConfig *c)
pid_t glfwWaylandCompositorPID(void)
unsigned long long glfwDBusUserNotify(const GLFWDBUSNotificationData *n, GLFWDBusnotificationcreatedfun callback, void *data)
void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler)
int glfwSetX11LaunchCommand(GLFWwindow *handle, char **argv, int argc)
void glfwSetX11WindowAsDock(int32_t x11_window_id)
void glfwSetX11WindowStrut(int32_t x11_window_id, uint32_t dimensions[12])
'''.splitlines():
if line:
functions.append(Function(line.strip(), check_fail=False))
@@ -358,7 +358,7 @@ def generate_wrappers(glfw_header: str) -> None:
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int,unsigned long);
typedef bool (* GLFWapplicationshouldhandlereopenfun)(int);
typedef bool (* GLFWhandleurlopen)(const char*);
typedef void (* GLFWapplicationwillfinishlaunchingfun)(bool);
typedef void (* GLFWapplicationwillfinishlaunchingfun)(void);
typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
typedef void (* GLFWcocoarenderframefun)(GLFWwindow*);
typedef void (*GLFWwaylandframecallbackfunc)(unsigned long long id);

49
glfw/glfw3.h vendored
View File

@@ -1056,7 +1056,6 @@ typedef enum {
#define GLFW_WAYLAND_APP_ID 0x00025001
#define GLFW_WAYLAND_BGCOLOR 0x00025002
#define GLFW_WAYLAND_WINDOW_TAG 0x00025003
/*! @} */
#define GLFW_NO_API 0
@@ -1301,32 +1300,19 @@ typedef struct GLFWkeyevent
bool fake_event_on_focus_change;
} 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, GLFW_EDGE_CENTER_SIZED } 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 struct GLFWLayerShellConfig {
GLFWLayerShellType type;
GLFWEdge edge;
struct {
GLFWEdge edge;
int requested_top_margin, requested_left_margin, requested_bottom_margin, requested_right_margin;
} previous;
bool was_toggled_to_fullscreen;
char output_name[128];
char output_name[64];
GLFWFocusPolicy focus_policy;
unsigned x_size_in_cells, x_size_in_pixels;
unsigned y_size_in_cells, y_size_in_pixels;
int requested_top_margin, requested_left_margin, requested_bottom_margin, requested_right_margin;
int requested_exclusive_zone, hide_on_focus_loss;
unsigned override_exclusive_zone;
void (*size_callback)(GLFWwindow *window, float xscale, float yscale, unsigned *cell_width, unsigned *cell_height, double *left_edge_spacing, double *top_edge_spacing, double *right_edge_spacing, double *bottom_edge_spacing);
struct { float xscale, yscale; } expected;
struct {
float background_opacity; int background_blur, color_space;
} related;
unsigned size_in_cells;
void (*size_callback)(GLFWwindow *window, const struct GLFWLayerShellConfig *config, unsigned monitor_width, unsigned monitor_height, uint32_t *width, uint32_t *height);
} GLFWLayerShellConfig;
typedef struct GLFWDBUSNotificationData {
@@ -1439,22 +1425,20 @@ typedef void (* GLFWwindowclosefun)(GLFWwindow*);
*/
typedef void (* GLFWapplicationclosefun)(int);
/*! @brief The function pointer type for system color theme change callbacks.
*
* This is the function pointer type for system color theme changes.
* @code
* void function_name(GLFWColorScheme theme_type, bool is_initial_value)
* void function_name(int theme_type)
* @endcode
*
* @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
*
* @ingroup window
*/
typedef void (* GLFWsystemcolorthemechangefun)(GLFWColorScheme, bool);
typedef void (* GLFWsystemcolorthemechangefun)(GLFWColorScheme);
/*! @brief The function pointer type for window content refresh callbacks.
@@ -1822,7 +1806,6 @@ typedef enum {
typedef GLFWDataChunk (* GLFWclipboarditerfun)(const char *mime_type, void *iter, GLFWClipboardType ctype);
typedef bool (* GLFWclipboardwritedatafun)(void *object, const char *data, size_t sz);
typedef bool (* GLFWimecursorpositionfun)(GLFWwindow *window, GLFWIMEUpdateEvent *ev);
typedef void (* GLFWclipboardlostfun )(GLFWClipboardType);
/*! @brief Video mode type.
*
@@ -1973,7 +1956,7 @@ typedef struct GLFWgamepadstate
*
* @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 glfwStopMainLoop(void);
GLFWAPI unsigned long long glfwAddTimer(monotonic_t interval, bool repeats, GLFWuserdatafun callback, void * callback_data, GLFWuserdatafun free_callback);
@@ -1983,7 +1966,6 @@ GLFWAPI GLFWdrawtextfun glfwSetDrawTextFunction(GLFWdrawtextfun function);
GLFWAPI GLFWcurrentselectionfun glfwSetCurrentSelectionCallback(GLFWcurrentselectionfun callback);
GLFWAPI GLFWhascurrentselectionfun glfwSetHasCurrentSelectionCallback(GLFWhascurrentselectionfun callback);
GLFWAPI GLFWimecursorpositionfun glfwSetIMECursorPositionCallback(GLFWimecursorpositionfun callback);
GLFWAPI bool glfwIsLayerShellSupported(void);
/*! @brief Terminates the GLFW library.
*
@@ -2386,7 +2368,6 @@ GLFWAPI void glfwGetMonitorContentScale(GLFWmonitor* monitor, float* xscale, flo
* @ingroup monitor
*/
GLFWAPI const char* glfwGetMonitorName(GLFWmonitor* monitor);
GLFWAPI const char* glfwGetMonitorDescription(GLFWmonitor* monitor);
/*! @brief Sets the user pointer of the specified monitor.
*
@@ -2877,12 +2858,10 @@ GLFWAPI void glfwWindowHintString(int hint, const char* value);
*
* @ingroup window
*/
GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height, const char* title, GLFWmonitor* monitor, GLFWwindow* share, const GLFWLayerShellConfig *lsc);
GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height, const char* title, GLFWmonitor* monitor, GLFWwindow* share);
GLFWAPI bool glfwToggleFullscreen(GLFWwindow *window, unsigned int flags);
GLFWAPI bool glfwIsFullscreen(GLFWwindow *window, unsigned int flags);
GLFWAPI bool glfwAreSwapsAllowed(const GLFWwindow* window);
GLFWAPI const GLFWLayerShellConfig* glfwGetLayerShellConfig(GLFWwindow* handle);
GLFWAPI bool glfwSetLayerShellConfig(GLFWwindow* handle, const GLFWLayerShellConfig *value);
/*! @brief Destroys the specified window and its context.
*
@@ -3013,6 +2992,10 @@ GLFWAPI void glfwSetWindowTitle(GLFWwindow* window, const char* title);
* [Bundle Programming Guide](https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFBundles/)
* 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.
*
* @sa @ref window_icon
@@ -3579,7 +3562,7 @@ GLFWAPI void glfwMaximizeWindow(GLFWwindow* window);
*
* @ingroup window
*/
GLFWAPI void glfwShowWindow(GLFWwindow* window, bool move_to_active_screen);
GLFWAPI void glfwShowWindow(GLFWwindow* window);
/*! @brief Hides the specified window.
*
@@ -3998,8 +3981,7 @@ GLFWAPI GLFWwindowsizefun glfwSetWindowSizeCallback(GLFWwindow* window, GLFWwind
GLFWAPI GLFWwindowclosefun glfwSetWindowCloseCallback(GLFWwindow* window, GLFWwindowclosefun callback);
GLFWAPI GLFWapplicationclosefun glfwSetApplicationCloseCallback(GLFWapplicationclosefun callback);
GLFWAPI GLFWsystemcolorthemechangefun glfwSetSystemColorThemeChangeCallback(GLFWsystemcolorthemechangefun callback);
GLFWAPI GLFWclipboardlostfun glfwSetClipboardLostCallback(GLFWclipboardlostfun callback);
GLFWAPI GLFWColorScheme glfwGetCurrentSystemColorTheme(bool query_if_unintialized);
GLFWAPI GLFWColorScheme glfwGetCurrentSystemColorTheme(void);
/*! @brief Sets the refresh callback for the specified window.
*
@@ -4247,7 +4229,6 @@ GLFWAPI void glfwPostEmptyEvent(void);
GLFWAPI bool glfwGetIgnoreOSKeyboardProcessing(void);
GLFWAPI void glfwSetIgnoreOSKeyboardProcessing(bool enabled);
GLFWAPI bool glfwGrabKeyboard(int grab);
/*! @brief Returns the value of an input option for the specified window.
*

4
glfw/glx_context.c vendored
View File

@@ -658,8 +658,6 @@ bool _glfwChooseVisualGLX(const _GLFWwndconfig* wndconfig UNUSED,
GLFWAPI GLXContext glfwGetGLXContext(GLFWwindow* handle)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
if (window->context.client == GLFW_NO_API)
@@ -674,8 +672,6 @@ GLFWAPI GLXContext glfwGetGLXContext(GLFWwindow* handle)
GLFWAPI GLXWindow glfwGetGLXWindow(GLFWwindow* handle)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(None);
if (window->context.client == GLFW_NO_API)

19
glfw/ibus_glfw.c vendored
View File

@@ -325,12 +325,7 @@ get_ibus_address_file_name(void) {
}
offset = snprintf(ans, sizeof(ans), "%s/.config", conf_env);
}
DBusError err;
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;
}
char *key = dbus_get_local_machine_id();
snprintf(ans + offset, sizeof(ans) - offset, "/ibus/bus/%s-%s-%s", key, host, disp_num);
dbus_free(key);
return ans;
@@ -373,9 +368,9 @@ read_ibus_address(_GLFWIBUSData *ibus) {
}
void
input_context_created(DBusMessage *msg, const DBusError *err, void *data) {
if (err) {
_glfwInputError(GLFW_PLATFORM_ERROR, "IBUS: Failed to create input context with error: %s: %s", err->name, err->message);
input_context_created(DBusMessage *msg, const char* errmsg, void *data) {
if (errmsg) {
_glfwInputError(GLFW_PLATFORM_ERROR, "IBUS: Failed to create input context with error: %s", errmsg);
return;
}
const char *path = NULL;
@@ -488,15 +483,15 @@ glfw_ibus_set_cursor_geometry(_GLFWIBUSData *ibus, int x, int y, int w, int h) {
}
void
key_event_processed(DBusMessage *msg, const DBusError *err, void *data) {
key_event_processed(DBusMessage *msg, const char* errmsg, void *data) {
uint32_t handled = 0;
_GLFWIBUSKeyEvent *ev = (_GLFWIBUSKeyEvent*)data;
// Restore key's text from the text embedded in the structure.
ev->glfw_ev.text = ev->__embedded_text;
bool is_release = ev->glfw_ev.action == GLFW_RELEASE;
bool failed = false;
if (err) {
_glfwInputError(GLFW_PLATFORM_ERROR, "IBUS: Failed to process key with error: %s: %s", err->name, err->message);
if (errmsg) {
_glfwInputError(GLFW_PLATFORM_ERROR, "IBUS: Failed to process key with error: %s", errmsg);
failed = true;
} else {
glfw_dbus_get_args(msg, "Failed to get IBUS handled key from reply", DBUS_TYPE_BOOLEAN, &handled, DBUS_TYPE_INVALID);

12
glfw/init.c vendored
View File

@@ -223,9 +223,8 @@ _glfwDebug(const char *format, ...) {
////// 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)
return true;
monotonic_start_time = start_time;
@@ -234,7 +233,7 @@ GLFWAPI int glfwInit(monotonic_t start_time, bool *supports_window_occlusion)
_glfw.hints.init = _glfwInitHints;
_glfw.ignoreOSKeyboardProcessing = false;
if (!_glfwPlatformInit(supports_window_occlusion))
if (!_glfwPlatformInit())
{
terminate();
return false;
@@ -395,13 +394,6 @@ GLFWAPI GLFWsystemcolorthemechangefun glfwSetSystemColorThemeChangeCallback(GLFW
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)
{

20
glfw/input.c vendored
View File

@@ -448,16 +448,11 @@ void _glfwInputJoystickHat(_GLFWjoystick* js, int hat, char value)
js->hats[hat] = value;
}
void _glfwInputColorScheme(GLFWColorScheme value, bool is_initial_value) {
void _glfwInputColorScheme(GLFWColorScheme value) {
_glfwPlatformInputColorScheme(value);
if (_glfw.callbacks.system_color_theme_change) _glfw.callbacks.system_color_theme_change(value, is_initial_value);
if (_glfw.callbacks.system_color_theme_change) _glfw.callbacks.system_color_theme_change(value);
}
void _glfwInputClipboardLost(GLFWClipboardType which) {
if (_glfw.callbacks.clipboard_lost) _glfw.callbacks.clipboard_lost(which);
}
//////////////////////////////////////////////////////////////////////////
////// GLFW internal API //////
//////////////////////////////////////////////////////////////////////////
@@ -684,13 +679,6 @@ GLFWAPI void glfwSetIgnoreOSKeyboardProcessing(bool enabled) {
_glfw.ignoreOSKeyboardProcessing = enabled;
}
GLFWAPI bool glfwGrabKeyboard(int grab) {
if (grab == 0 || grab == 1) {
if (_glfwPlatformGrabKeyboard(grab)) _glfw.keyboard_grabbed = grab;
}
return _glfw.keyboard_grabbed;
}
GLFWAPI int glfwGetInputMode(GLFWwindow* handle, int mode)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
@@ -740,7 +728,9 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* handle, int mode, int value)
window->cursorMode = value;
_glfwPlatformGetCursorPos(window, &window->virtualCursorPosX, &window->virtualCursorPosY);
_glfwPlatformGetCursorPos(window,
&window->virtualCursorPosX,
&window->virtualCursorPosY);
_glfwPlatformSetCursorMode(window, value);
}
else if (mode == GLFW_STICKY_KEYS)

27
glfw/internal.h vendored
View File

@@ -331,7 +331,7 @@ struct _GLFWwndconfig
char instanceName[256];
} x11;
struct {
char appId[256], windowTag[256];
char appId[256];
uint32_t bgcolor;
} wl;
};
@@ -490,7 +490,7 @@ struct _GLFWwindow
//
struct _GLFWmonitor
{
const char *name, *description;
char* name;
void* userPointer;
// Physical dimensions in millimeters.
@@ -616,7 +616,7 @@ struct _GLFWlibrary
_GLFWtls contextSlot;
_GLFWmutex errorLock;
bool ignoreOSKeyboardProcessing, keyboard_grabbed;
bool ignoreOSKeyboardProcessing;
struct {
bool available;
@@ -644,7 +644,6 @@ struct _GLFWlibrary
GLFWmonitorfun monitor;
GLFWjoystickfun joystick;
GLFWapplicationclosefun application_close;
GLFWclipboardlostfun clipboard_lost;
GLFWsystemcolorthemechangefun system_color_theme_change;
GLFWdrawtextfun draw_text;
GLFWcurrentselectionfun get_current_selection;
@@ -668,16 +667,12 @@ struct _GLFWlibrary
//
extern _GLFWlibrary _glfw;
typedef struct GeometryRect { int x, y, width, height; } GeometryRect;
typedef struct MonitorGeometry {
GeometryRect full, workarea;
} MonitorGeometry;
//////////////////////////////////////////////////////////////////////////
////// GLFW platform API //////
//////////////////////////////////////////////////////////////////////////
int _glfwPlatformInit(bool*);
int _glfwPlatformInit(void);
void _glfwPlatformTerminate(void);
const char* _glfwPlatformGetVersionString(void);
@@ -713,13 +708,14 @@ void _glfwPlatformTerminateJoysticks(void);
int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode);
void _glfwPlatformUpdateGamepadGUID(char* guid);
int _glfwPlatformCreateWindow(_GLFWwindow* window, const _GLFWwndconfig* wndconfig, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig, const GLFWLayerShellConfig *lsc);
int _glfwPlatformCreateWindow(_GLFWwindow* window,
const _GLFWwndconfig* wndconfig,
const _GLFWctxconfig* ctxconfig,
const _GLFWfbconfig* fbconfig);
void _glfwPlatformDestroyWindow(_GLFWwindow* window);
void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title);
void _glfwPlatformSetWindowIcon(_GLFWwindow* window,
int count, const GLFWimage* images);
bool _glfwPlatformSetLayerShellConfig(_GLFWwindow* window, const GLFWLayerShellConfig *value);
const GLFWLayerShellConfig* _glfwPlatformGetLayerShellConfig(_GLFWwindow* window);
void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos);
void _glfwPlatformSetWindowPos(_GLFWwindow* window, int xpos, int ypos);
void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height);
@@ -740,7 +736,7 @@ monotonic_t _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window);
void _glfwPlatformIconifyWindow(_GLFWwindow* window);
void _glfwPlatformRestoreWindow(_GLFWwindow* window);
void _glfwPlatformMaximizeWindow(_GLFWwindow* window);
void _glfwPlatformShowWindow(_GLFWwindow* window, bool move_to_active_screen);
void _glfwPlatformShowWindow(_GLFWwindow* window);
void _glfwPlatformHideWindow(_GLFWwindow* window);
void _glfwPlatformRequestWindowAttention(_GLFWwindow* window);
int _glfwPlatformWindowBell(_GLFWwindow* window);
@@ -813,13 +809,12 @@ void _glfwInputWindowCloseRequest(_GLFWwindow* window);
void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor);
void _glfwInputKeyboard(_GLFWwindow *window, GLFWkeyevent *ev);
void _glfwInputClipboardLost(GLFWClipboardType which);
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags, int mods);
void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods);
void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos);
void _glfwInputCursorEnter(_GLFWwindow* window, bool entered);
int _glfwInputDrop(_GLFWwindow* window, const char *mime, const char *text, size_t sz);
void _glfwInputColorScheme(GLFWColorScheme, bool);
void _glfwInputColorScheme(GLFWColorScheme);
void _glfwPlatformInputColorScheme(GLFWColorScheme);
void _glfwInputJoystick(_GLFWjoystick* js, int event);
void _glfwInputJoystickAxis(_GLFWjoystick* js, int axis, float value);
@@ -883,8 +878,6 @@ unsigned long long _glfwPlatformAddTimer(monotonic_t interval, bool repeats, GLF
void _glfwPlatformUpdateTimer(unsigned long long timer_id, monotonic_t interval, bool enabled);
void _glfwPlatformRemoveTimer(unsigned long long timer_id);
int _glfwPlatformSetWindowBlur(_GLFWwindow* handle, int value);
MonitorGeometry _glfwPlatformGetMonitorGeometry(_GLFWmonitor* monitor);
bool _glfwPlatformGrabKeyboard(bool grab);
char* _glfw_strdup(const char* source);

View File

@@ -15,82 +15,26 @@
#define DESKTOP_INTERFACE "org.freedesktop.portal.Settings"
#define GNOME_DESKTOP_NAMESPACE "org.gnome.desktop.interface"
#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"
static char theme_name[128] = {0};
static int theme_size = -1;
static GLFWColorScheme appearance = GLFW_COLOR_SCHEME_NO_PREFERENCE;
static bool cursor_theme_changed = false, appearance_initialized = false;
#define HANDLER(name_) static void name_(DBusMessage *msg, const DBusError* err, void *data) { \
(void)data; \
if (err) { \
_glfwInputError(GLFW_PLATFORM_ERROR, "%s: failed with error: %s: %s", #name_, err->name, err->message); \
return; \
}
HANDLER(get_color_scheme_legacy)
DBusMessageIter iter, variant_iter, variant_iter2;
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_VARIANT) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Read for color-scheme did not return a variant"); return;
}
dbus_message_iter_recurse(&variant_iter, &variant_iter2);
if (type != DBUS_TYPE_VARIANT) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Read for color-scheme did not return a nested variant"); return;
}
uint32_t val;
dbus_message_iter_get_basic(&variant_iter2, &val);
if (val < 3) appearance = val;
}
static void
get_color_scheme(DBusMessage *msg, const DBusError* err, void *data) {
(void) data;
if (err) {
if (strcmp("org.freedesktop.DBus.Error.UnknownMethod", err->name) == 0) {
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, "Read", DBUS_TIMEOUT_USE_DEFAULT,
get_color_scheme_legacy, NULL, DBUS_TYPE_STRING, &namespace, DBUS_TYPE_STRING, &key, DBUS_TYPE_INVALID);
}
return;
} else {
_glfwInputError(GLFW_PLATFORM_ERROR, "%s: failed with error: %s: %s", "get_color_scheme", err->name, err->message);
return;
}
}
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;
}
static bool cursor_theme_changed = false;
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);
}
}
glfw_current_system_color_theme(void) {
return appearance;
}
#define HANDLER(name) static void name(DBusMessage *msg, const char* errmsg, void *data) { \
(void)data; \
if (errmsg) { \
_glfwInputError(GLFW_PLATFORM_ERROR, "%s: failed with error: %s", #name, errmsg); \
return; \
}
static void
process_fdo_setting(const char *key, DBusMessageIter *value) {
if (strcmp(key, FDO_APPEARANCE_KEY) == 0) {
@@ -98,13 +42,7 @@ process_fdo_setting(const char *key, DBusMessageIter *value) {
uint32_t val;
dbus_message_iter_get_basic(value, &val);
if (val > 2) val = 0;
if (!appearance_initialized) {
appearance_initialized = true;
if (val != appearance) {
appearance = val;
_glfwInputColorScheme(appearance, true);
}
}
appearance = val;
}
}
}
@@ -187,11 +125,8 @@ read_desktop_settings(DBusConnection *session_bus) {
DBusMessageIter iter, array_iter;
dbus_message_iter_init_append(msg, &iter);
if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &array_iter)) { return false; }
for (unsigned i = 0; i < arraysz(supported_namespaces); ++i) {
if (!dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_STRING, &supported_namespaces[i])) return false;
}
if (!dbus_message_iter_close_container(&iter, &array_iter)) { return false; }
return call_method_with_msg(session_bus, msg, DBUS_TIMEOUT_USE_DEFAULT, process_desktop_settings, NULL, false);
return call_method_with_msg(session_bus, msg, DBUS_TIMEOUT_USE_DEFAULT, process_desktop_settings, NULL);
}
void
@@ -226,8 +161,7 @@ on_color_scheme_change(DBusMessage *message) {
if (val > 2) val = 0;
if (val != appearance) {
appearance = val;
appearance_initialized = true;
_glfwInputColorScheme(appearance, false);
_glfwInputColorScheme(appearance);
}
}
break;
@@ -259,7 +193,7 @@ glfw_initialize_desktop_settings(void) {
get_cursor_theme_from_env();
DBusConnection *session_bus = glfw_dbus_session_bus();
if (session_bus) {
if (!read_desktop_settings(session_bus)) _glfwInputError(GLFW_PLATFORM_ERROR, "WARNING: Failed to read desktop settings, using defaults, make sure you have the desktop portal running.");
if (!read_desktop_settings(session_bus)) _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to read desktop settings, make sure you have the desktop portal running.");
dbus_bus_add_match(session_bus, "type='signal',interface='" DESKTOP_INTERFACE "',member='SettingChanged'", NULL);
dbus_connection_add_filter(session_bus, setting_changed, NULL, NULL);
}

View File

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

12
glfw/linux_notify.c vendored
View File

@@ -32,9 +32,9 @@ glfw_dbus_set_user_notification_activated_handler(GLFWDBusnotificationactivatedf
}
void
notification_created(DBusMessage *msg, const DBusError* err, void *data) {
if (err) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Notify: Failed to create notification error: %s: %s", err->name, err->message);
notification_created(DBusMessage *msg, const char* errmsg, void *data) {
if (errmsg) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Notify: Failed to create notification error: %s", errmsg);
if (data) free(data);
return;
}
@@ -95,9 +95,9 @@ cancel_user_notification(DBusConnection *session_bus, uint32_t *id) {
}
static void
got_capabilities(DBusMessage *msg, const DBusError* err, void* data UNUSED) {
got_capabilities(DBusMessage *msg, const char* err, void* data UNUSED) {
if (err) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Notify: Failed to get server capabilities error: %s: %s", err->name, err->message);
_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; }
@@ -184,7 +184,7 @@ glfw_dbus_send_user_notification(const GLFWDBUSNotificationData *n, GLFWDBusnoti
APPEND(args, DBUS_TYPE_INT32, n->timeout)
#undef check_call
#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;
data = NULL;
return ans;

15
glfw/monitor.c vendored
View File

@@ -186,8 +186,7 @@ void _glfwFreeMonitor(_GLFWmonitor* monitor)
_glfwFreeGammaArrays(&monitor->currentRamp);
free(monitor->modes);
free((void*)monitor->name);
free((void*)monitor->description);
free(monitor->name);
free(monitor);
}
@@ -394,19 +393,9 @@ GLFWAPI const char* glfwGetMonitorName(GLFWmonitor* handle)
assert(monitor != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
return monitor->name ? monitor->name : "";
return monitor->name;
}
GLFWAPI const char* glfwGetMonitorDescription(GLFWmonitor* handle)
{
_GLFWmonitor* monitor = (_GLFWmonitor*) handle;
assert(monitor != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
return monitor->description ? monitor->description : "";
}
GLFWAPI void glfwSetMonitorUserPointer(GLFWmonitor* handle, void* pointer)
{
_GLFWmonitor* monitor = (_GLFWmonitor*) handle;

View File

@@ -27,7 +27,6 @@
//========================================================================
#include "internal.h"
#include <assert.h>
static void makeContextCurrentNSGL(_GLFWwindow* window)
@@ -107,22 +106,12 @@ bool _glfwInitNSGL(void)
return true;
}
// We use a globally shared context across all OS Windows so that
// if all OS Windows are closed the context does not have to be recreated
// with all associated data lost. On Apple kitty can remain running with no
// OS Windows.
static NSOpenGLContext* globally_shared_context = nil;
// Terminate OpenGL support
//
void _glfwTerminateNSGL(void) {
if (globally_shared_context != nil) {
[globally_shared_context release];
globally_shared_context = nil;
}
void _glfwTerminateNSGL(void)
{
}
// Create the OpenGL context
//
bool _glfwCreateContextNSGL(_GLFWwindow* window,
@@ -282,7 +271,7 @@ bool _glfwCreateContextNSGL(_GLFWwindow* window,
return false;
}
NSOpenGLContext* share = globally_shared_context;
NSOpenGLContext* share = nil;
if (ctxconfig->share)
share = ctxconfig->share->context.nsgl.object;
@@ -296,9 +285,6 @@ bool _glfwCreateContextNSGL(_GLFWwindow* window,
"NSGL: Failed to create OpenGL context");
return false;
}
if (globally_shared_context == nil) {
globally_shared_context = [window->context.nsgl.object retain];
}
if (fbconfig->transparent)
{
@@ -333,8 +319,6 @@ bool _glfwCreateContextNSGL(_GLFWwindow* window,
GLFWAPI id glfwGetNSGLContext(GLFWwindow* handle)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(nil);
if (window->context.client == GLFW_NO_API)

View File

@@ -352,8 +352,6 @@ GLFWAPI int glfwGetOSMesaDepthBuffer(GLFWwindow* handle,
GLFWAPI OSMesaContext glfwGetOSMesaContext(GLFWwindow* handle)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
if (window->context.client == GLFW_NO_API)

View File

@@ -13,7 +13,6 @@
"cocoa_joystick.m",
"cocoa_monitor.m",
"cocoa_window.m",
"cocoa_displaylink.m",
"posix_thread.c",
"nsgl_context.m",
"egl_context.c",
@@ -84,11 +83,6 @@
"staging/fractional-scale/fractional-scale-v1.xml",
"staging/single-pixel-buffer/single-pixel-buffer-v1.xml",
"unstable/idle-inhibit/idle-inhibit-unstable-v1.xml",
"unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml",
"staging/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml",
"staging/xdg-system-bell/xdg-system-bell-v1.xml",
"staging/xdg-toplevel-tag/xdg-toplevel-tag-v1.xml",
"staging/color-management/color-management-v1.xml",
"kwin-blur-v1.xml",
"wlr-layer-shell-unstable-v1.xml"

35
glfw/window.c vendored
View File

@@ -187,7 +187,11 @@ void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor)
////// GLFW public API //////
//////////////////////////////////////////////////////////////////////////
GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height, const char* title, GLFWmonitor* monitor, GLFWwindow* share, const GLFWLayerShellConfig *lsc) {
GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
const char* title,
GLFWmonitor* monitor,
GLFWwindow* share)
{
_GLFWfbconfig fbconfig;
_GLFWctxconfig ctxconfig;
_GLFWwndconfig wndconfig;
@@ -252,7 +256,7 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height, const char* title, G
window->heightincr = GLFW_DONT_CARE;
// Open the actual window and create its context
if (!_glfwPlatformCreateWindow(window, &wndconfig, &ctxconfig, &fbconfig, lsc))
if (!_glfwPlatformCreateWindow(window, &wndconfig, &ctxconfig, &fbconfig))
{
glfwDestroyWindow((GLFWwindow*) window);
return NULL;
@@ -279,7 +283,7 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height, const char* title, G
{
if (wndconfig.visible)
{
_glfwPlatformShowWindow(window, false);
_glfwPlatformShowWindow(window);
#ifndef _GLFW_WAYLAND
if (wndconfig.focused)
_glfwPlatformFocusWindow(window);
@@ -495,10 +499,6 @@ GLFWAPI void glfwWindowHintString(int hint, const char* value)
strncpy(_glfw.hints.window.wl.appId, value,
sizeof(_glfw.hints.window.wl.appId) - 1);
return;
case GLFW_WAYLAND_WINDOW_TAG:
strncpy(_glfw.hints.window.wl.windowTag, value,
sizeof(_glfw.hints.window.wl.windowTag) - 1);
return;
}
_glfwInputError(GLFW_INVALID_ENUM, "Invalid window hint string 0x%08X", hint);
@@ -555,23 +555,6 @@ GLFWAPI void glfwSetWindowShouldClose(GLFWwindow* handle, int value)
window->shouldClose = value;
}
GLFWAPI const GLFWLayerShellConfig* glfwGetLayerShellConfig(GLFWwindow* handle) {
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(false);
return _glfwPlatformGetLayerShellConfig(window);
}
GLFWAPI bool glfwSetLayerShellConfig(GLFWwindow* handle, const GLFWLayerShellConfig *value) {
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(false);
return _glfwPlatformSetLayerShellConfig(window, value);
}
GLFWAPI void glfwSetWindowTitle(GLFWwindow* handle, const char* title)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
@@ -855,7 +838,7 @@ GLFWAPI void glfwMaximizeWindow(GLFWwindow* handle)
_glfwPlatformMaximizeWindow(window);
}
GLFWAPI void glfwShowWindow(GLFWwindow* handle, bool move_to_active_screen)
GLFWAPI void glfwShowWindow(GLFWwindow* handle)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
@@ -865,7 +848,7 @@ GLFWAPI void glfwShowWindow(GLFWwindow* handle, bool move_to_active_screen)
if (window->monitor)
return;
_glfwPlatformShowWindow(window, move_to_active_screen);
_glfwPlatformShowWindow(window);
#ifndef _GLFW_WAYLAND
if (window->focusOnShow)

View File

@@ -92,12 +92,12 @@ 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);
free(st.data);
st.data = malloc(sizeof(st.data[0]) * st.stride * st.stride);
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;
@@ -210,7 +210,7 @@ scale(unsigned thickness, float factor) {
static void
render_minimize(uint8_t *out, unsigned width, unsigned height) {
memset(out, 0, (size_t)width * height);
memset(out, 0, width * height);
unsigned thickness = height / 12;
unsigned baseline = height - thickness * 2;
unsigned side_margin = scale(thickness, 3.8f);
@@ -220,7 +220,7 @@ render_minimize(uint8_t *out, unsigned width, unsigned height) {
static void
render_maximize(uint8_t *out, unsigned width, unsigned height) {
memset(out, 0, (size_t)width * 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);
@@ -234,7 +234,7 @@ render_maximize(uint8_t *out, unsigned width, unsigned height) {
static void
render_restore(uint8_t *out, unsigned width, unsigned height) {
memset(out, 0, (size_t)width * 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);
@@ -277,7 +277,7 @@ render_line(uint8_t *buf, unsigned width, unsigned height, unsigned thickness, i
static void
render_close(uint8_t *out, unsigned width, unsigned height) {
memset(out, 0, (size_t)width * height);
memset(out, 0, width * height);
unsigned thickness = height / 12;
unsigned baseline = height - thickness * 2;
unsigned side_margin = scale(thickness, 3.3f);
@@ -313,10 +313,10 @@ 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((size_t)factor * factor * height * src_width);
uint8_t *big_src = malloc(factor * factor * height * src_width);
if (big_src) {
which(big_src, src_width * factor, height * factor);
memset(src, 0, (size_t)src_width * height);
memset(src, 0, src_width * height);
downsample(src, big_src, src_width, height, factor);
free(big_src);
} else which(src, src_width, height);
@@ -332,7 +332,7 @@ render_title_bar(_GLFWwindow *window, bool to_front_buffer) {
const uint32_t dark_fg = is_focused ? 0xffffffff : 0xffcccccc, dark_bg = is_focused ? 0xff303030 : 0xff242424;
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);
GLFWColorScheme appearance = glfwGetCurrentSystemColorTheme();
bool is_dark = false;
if (decs.use_custom_titlebar_color || appearance == GLFW_COLOR_SCHEME_NO_PREFERENCE) {
bg_color = 0xff000000 | (decs.titlebar_color & 0xffffff);
@@ -346,8 +346,8 @@ render_title_bar(_GLFWwindow *window, bool to_front_buffer) {
uint8_t *output = to_front_buffer ? decs.titlebar.buffer.data.front : decs.titlebar.buffer.data.back;
// render text part
size_t button_size = decs.titlebar.buffer.height;
unsigned num_buttons = 1;
int button_size = decs.titlebar.buffer.height;
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) {
@@ -468,8 +468,10 @@ render_shadows(_GLFWwindow *window) {
static bool
create_shm_buffers(_GLFWwindow* window) {
const double scale = _glfwWaylandWindowScale(window);
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);
#define bp(which, width, height) decs.mapping.size += init_buffer_pair(&decs.which.buffer, width, height, scale);
bp(titlebar, window->wl.width, decs.metrics.visible_titlebar_height);
bp(shadow_top, window->wl.width, decs.metrics.width);
bp(shadow_bottom, window->wl.width, decs.metrics.width);
@@ -502,7 +504,7 @@ create_shm_buffers(_GLFWwindow* window) {
wl_shm_pool_destroy(pool);
render_title_bar(window, true);
render_shadows(window);
debug("Created decoration buffers at scale: %f\n", decs.for_window_state.fscale);
debug("Created decoration buffers at scale: %f\n", scale);
return true;
}
@@ -570,21 +572,15 @@ window_is_csd_capable(_GLFWwindow *window) {
return window->decorated && !decs.serverSide && window->wl.xdg.toplevel;
}
bool
csd_should_window_be_decorated(_GLFWwindow *window) {
return window_is_csd_capable(window) && window->monitor == NULL && (window->wl.current.toplevel_states & TOPLEVEL_STATE_FULLSCREEN) == 0;
}
static bool
ensure_csd_resources(_GLFWwindow *window) {
if (!window_is_csd_capable(window)) return false;
const bool is_focused = window->id == _glfw.focusedWindowId;
const bool focus_changed = is_focused != decs.for_window_state.focused;
const double current_scale = _glfwWaylandWindowScale(window);
const bool size_changed = (
decs.for_window_state.width != window->wl.width ||
decs.for_window_state.height != window->wl.height ||
decs.for_window_state.fscale != current_scale ||
decs.for_window_state.fscale != _glfwWaylandWindowScale(window) ||
!decs.mapping.data
);
const bool state_changed = decs.for_window_state.toplevel_states != window->wl.current.toplevel_states;
@@ -593,7 +589,6 @@ ensure_csd_resources(_GLFWwindow *window) {
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;
decs.for_window_state.fscale = current_scale; // used in create_shm_buffers
if (size_changed || decs.buffer_destroyed) {
free_csd_buffers(window);
if (!create_shm_buffers(window)) return false;
@@ -623,6 +618,7 @@ ensure_csd_resources(_GLFWwindow *window) {
decs.for_window_state.width = window->wl.width;
decs.for_window_state.height = window->wl.height;
decs.for_window_state.fscale = _glfwWaylandWindowScale(window);
decs.for_window_state.focused = is_focused;
decs.for_window_state.toplevel_states = window->wl.current.toplevel_states;
return true;
@@ -658,18 +654,18 @@ csd_change_title(_GLFWwindow *window) {
void
csd_set_window_geometry(_GLFWwindow *window, int32_t *width, int32_t *height) {
const bool include_space_for_csd = csd_should_window_be_decorated(window);
bool has_csd = window_is_csd_capable(window) && decs.titlebar.surface && !(window->wl.current.toplevel_states & TOPLEVEL_STATE_FULLSCREEN);
bool size_specified_by_compositor = *width > 0 && *height > 0;
if (!size_specified_by_compositor) {
*width = window->wl.user_requested_content_size.width;
*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 (include_space_for_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.width = *width; decs.geometry.height = *height;
if (include_space_for_csd) {
if (has_csd) {
decs.geometry.y = -decs.metrics.visible_titlebar_height;
*height -= decs.metrics.visible_titlebar_height;
}
@@ -707,7 +703,7 @@ set_cursor(GLFWCursorShape shape, _GLFWwindow* window)
struct wl_cursor_theme *theme = glfw_wlc_theme_for_scale(scale);
if (!theme) return;
cursor = _glfwLoadCursor(shape, theme);
if (!cursor || !cursor->images) return;
if (!cursor) return;
image = cursor->images[0];
if (!image) return;
if (image->width % scale || image->height % scale) {
@@ -764,7 +760,6 @@ handle_pointer_leave(_GLFWwindow *window, struct wl_surface *surface) {
}
#undef c
decs.focus = CENTRAL_WINDOW;
decs.dragging = false;
}
@@ -774,11 +769,7 @@ handle_pointer_move(_GLFWwindow *window) {
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_titlebar: 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;
@@ -799,7 +790,6 @@ handle_pointer_enter(_GLFWwindow *window, struct wl_surface *surface) {
all_surfaces(Q)
#undef Q
decs.focus = CENTRAL_WINDOW;
decs.dragging = false;
}
static void
@@ -827,7 +817,9 @@ handle_pointer_button(_GLFWwindow *window, uint32_t button, uint32_t state) {
decs.maximize.hovered = false; decs.titlebar_needs_update = true;
} else if (decs.close.hovered) _glfwInputWindowCloseRequest(window);
}
decs.dragging = !has_hovered_button(window);
if (!has_hovered_button(window)) {
if (window->wl.xdg.toplevel) xdg_toplevel_move(window->wl.xdg.toplevel, _glfw.wl.seat, _glfw.wl.pointer_serial);
}
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;

View File

@@ -13,6 +13,5 @@ void csd_free_all_resources(_GLFWwindow *window);
bool csd_change_title(_GLFWwindow *window);
void 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 csd_should_window_be_decorated(_GLFWwindow *window);
void csd_set_visible(_GLFWwindow *window, bool visible);
void csd_handle_pointer_event(_GLFWwindow *window, int button, int state, struct wl_surface* surface);

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