mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-14 12:37:48 +02:00
Compare commits
145 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
656d2179c9 | ||
|
|
da582b5622 | ||
|
|
ba4292e912 | ||
|
|
785726d21d | ||
|
|
e3a155266e | ||
|
|
e3eb179be2 | ||
|
|
e88ae3397f | ||
|
|
8e0ef0c430 | ||
|
|
02df66733e | ||
|
|
4a038ea581 | ||
|
|
e41d57dffd | ||
|
|
b63be88bac | ||
|
|
8bfff51d23 | ||
|
|
72268539ef | ||
|
|
5b83a33888 | ||
|
|
daaec1b47f | ||
|
|
bc56fce38d | ||
|
|
788b3dc4b2 | ||
|
|
f4e22ebe3c | ||
|
|
349c32f60e | ||
|
|
3f919db0c7 | ||
|
|
c4dad85f99 | ||
|
|
a164c73389 | ||
|
|
3e430e1a70 | ||
|
|
12314cc33f | ||
|
|
c3bba2e926 | ||
|
|
a4f67b7424 | ||
|
|
b217c9acde | ||
|
|
23e777ea9e | ||
|
|
ecb106e92c | ||
|
|
23deaae5e7 | ||
|
|
bc6230d90c | ||
|
|
03f35812eb | ||
|
|
7c0007c1bd | ||
|
|
d2d2f6c503 | ||
|
|
69fb2e4231 | ||
|
|
94d056ed4f | ||
|
|
726f62b948 | ||
|
|
7e15839141 | ||
|
|
13a6ff25a2 | ||
|
|
e050557db7 | ||
|
|
3e2b3a89ce | ||
|
|
81c30cc5fa | ||
|
|
73a6668b17 | ||
|
|
f8e2dc1eca | ||
|
|
32b8077c89 | ||
|
|
b90fede2c1 | ||
|
|
87d1a97486 | ||
|
|
98450a0605 | ||
|
|
29377db94c | ||
|
|
8d8c2d7170 | ||
|
|
5be4f7b566 | ||
|
|
ce74706c95 | ||
|
|
8f8da2d2c6 | ||
|
|
540e11fa01 | ||
|
|
a230eb87a1 | ||
|
|
4ce7da044f | ||
|
|
ceccacafa8 | ||
|
|
df80da7cb5 | ||
|
|
08bffde4ad | ||
|
|
983bf134a1 | ||
|
|
e25c61f781 | ||
|
|
d6026c377b | ||
|
|
a766a95de6 | ||
|
|
36d906d4f5 | ||
|
|
881b72fea7 | ||
|
|
d6a7d4a8a1 | ||
|
|
db092eb88d | ||
|
|
33df5355a6 | ||
|
|
99e279cbe9 | ||
|
|
82ffb376b2 | ||
|
|
fc72f4961e | ||
|
|
880078cab8 | ||
|
|
88058c9075 | ||
|
|
ecdea9d4d3 | ||
|
|
5ace209024 | ||
|
|
b68aac9f28 | ||
|
|
71bbf4ecb9 | ||
|
|
1590462038 | ||
|
|
1735404a05 | ||
|
|
3c18d20d1b | ||
|
|
e51a1362ca | ||
|
|
5cea5cce81 | ||
|
|
bb722ed6dc | ||
|
|
78e81ec589 | ||
|
|
80721e1e63 | ||
|
|
7186b862f2 | ||
|
|
e4d82074ff | ||
|
|
804ad62a33 | ||
|
|
4431cff7fa | ||
|
|
78b77b0d01 | ||
|
|
2c2fbd4445 | ||
|
|
333e94622e | ||
|
|
41869d88c2 | ||
|
|
65b790df40 | ||
|
|
5d74d210ee | ||
|
|
431fc98659 | ||
|
|
21d3759f62 | ||
|
|
24dea14795 | ||
|
|
94628dca21 | ||
|
|
d6016f4246 | ||
|
|
688736c4cf | ||
|
|
1ce31a339e | ||
|
|
d86da0616e | ||
|
|
926dfd7ba1 | ||
|
|
e1b367e1b3 | ||
|
|
4998fe66b9 | ||
|
|
7f965eba5f | ||
|
|
0b743464fb | ||
|
|
ffed63a048 | ||
|
|
96c17b0a67 | ||
|
|
8a0b562f4f | ||
|
|
198aec84c2 | ||
|
|
217ded7b3f | ||
|
|
7522675553 | ||
|
|
8a8158f287 | ||
|
|
1646c297b3 | ||
|
|
de9d9fd157 | ||
|
|
c2e0ecef13 | ||
|
|
6e55949094 | ||
|
|
21f824e825 | ||
|
|
086185e409 | ||
|
|
e3e7bded06 | ||
|
|
7e232b33be | ||
|
|
53be33f14d | ||
|
|
54b50858ec | ||
|
|
46d75ea48f | ||
|
|
bf73720805 | ||
|
|
eb580ed91b | ||
|
|
7f8ce387be | ||
|
|
8ddd20770b | ||
|
|
53ea650c27 | ||
|
|
3b84498af9 | ||
|
|
fa52066156 | ||
|
|
e2e33cc11c | ||
|
|
71a8801a68 | ||
|
|
e54e17acf8 | ||
|
|
b57cb95522 | ||
|
|
12fd1472f5 | ||
|
|
5a86960acd | ||
|
|
dbbc0ea9d4 | ||
|
|
39f1ff6ead | ||
|
|
2d088889f7 | ||
|
|
48a0a7fe82 | ||
|
|
fbeead1661 |
18
.github/workflows/ci.py
vendored
18
.github/workflows/ci.py
vendored
@@ -15,7 +15,6 @@ 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_macos = 'darwin' in sys.platform.lower()
|
||||
SW = ''
|
||||
@@ -67,17 +66,7 @@ def install_fonts() -> None:
|
||||
fonts_dir = os.path.expanduser('~/Library/Fonts' if is_macos else '~/.local/share/fonts')
|
||||
os.makedirs(fonts_dir, exist_ok=True)
|
||||
with tarfile.open(fileobj=io.BytesIO(data), mode='r:xz') as tf:
|
||||
try:
|
||||
tf.extractall(fonts_dir, filter='fully_trusted')
|
||||
except TypeError:
|
||||
tf.extractall(fonts_dir)
|
||||
with urlopen(NERD_URL) as f:
|
||||
data = f.read()
|
||||
with tarfile.open(fileobj=io.BytesIO(data), mode='r:xz') as tf:
|
||||
try:
|
||||
tf.extractall(fonts_dir, filter='fully_trusted')
|
||||
except TypeError:
|
||||
tf.extractall(fonts_dir)
|
||||
tf.extractall(fonts_dir)
|
||||
|
||||
|
||||
def install_deps() -> None:
|
||||
@@ -161,10 +150,7 @@ def install_bundle() -> None:
|
||||
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('.'):
|
||||
|
||||
32
.github/workflows/ci.yml
vendored
32
.github/workflows/ci.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
cc: [gcc, clang]
|
||||
include:
|
||||
- python: a
|
||||
pyver: "3.9"
|
||||
pyver: "3.8"
|
||||
sanitize: 0
|
||||
|
||||
- python: b
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
sanitize: 1
|
||||
|
||||
- python: c
|
||||
pyver: "3.11"
|
||||
pyver: "3.9"
|
||||
sanitize: 1
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.12"
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v4
|
||||
@@ -188,29 +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@v3
|
||||
with:
|
||||
fetch-depth: 10
|
||||
|
||||
- name: Install build deps
|
||||
run: sudo apt-get update && sudo apt-get install -y curl xz-utils build-essential git pkg-config libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libgl1-mesa-dev libxkbcommon-x11-dev libfontconfig-dev libx11-xcb-dev libdbus-1-dev
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Build kitty
|
||||
run: ./dev.sh build
|
||||
|
||||
- name: Run benchmarks
|
||||
run: ./benchmark.py
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,7 +12,6 @@
|
||||
/dependencies
|
||||
/tags
|
||||
/build/
|
||||
/fonts/
|
||||
/linux-package/
|
||||
/kitty.app/
|
||||
/glad/out/
|
||||
|
||||
1140
3rdparty/uthash.h
vendored
Normal file
1140
3rdparty/uthash.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1946
3rdparty/verstable.h
vendored
1946
3rdparty/verstable.h
vendored
File diff suppressed because it is too large
Load Diff
@@ -11,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"]
|
||||
|
||||
90
benchmark.py
90
benchmark.py
@@ -1,90 +0,0 @@
|
||||
#!./kitty/launcher/kitty +launch
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import fcntl
|
||||
import io
|
||||
import os
|
||||
import select
|
||||
import signal
|
||||
import struct
|
||||
import sys
|
||||
import termios
|
||||
import time
|
||||
from pty import CHILD, fork
|
||||
|
||||
from kitty.constants import kitten_exe
|
||||
from kitty.fast_data_types import Screen, safe_pipe
|
||||
from kitty.utils import read_screen_size
|
||||
|
||||
|
||||
def run_parsing_benchmark(cell_width: int = 10, cell_height: int = 20, scrollback: int = 20000) -> None:
|
||||
isatty = sys.stdout.isatty()
|
||||
if isatty:
|
||||
sz = read_screen_size()
|
||||
columns, rows = sz.cols, sz.rows
|
||||
else:
|
||||
columns, rows = 80, 25
|
||||
child_pid, master_fd = fork()
|
||||
is_child = child_pid == CHILD
|
||||
argv = [kitten_exe(), '__benchmark__', '--with-scrollback']
|
||||
if is_child:
|
||||
while read_screen_size().width != columns * cell_width:
|
||||
time.sleep(0.01)
|
||||
signal.pthread_sigmask(signal.SIG_SETMASK, ())
|
||||
os.execvp(argv[0], argv)
|
||||
# os.set_blocking(master_fd, False)
|
||||
x_pixels = columns * cell_width
|
||||
y_pixels = rows * cell_height
|
||||
s = struct.pack('HHHH', rows, columns, x_pixels, y_pixels)
|
||||
fcntl.ioctl(master_fd, termios.TIOCSWINSZ, s)
|
||||
|
||||
write_buf = b''
|
||||
r_pipe, w_pipe = safe_pipe(True)
|
||||
class ToChild:
|
||||
def write(self, x: bytes | str) -> None:
|
||||
nonlocal write_buf
|
||||
if isinstance(x, str):
|
||||
x = x.encode()
|
||||
write_buf += x
|
||||
os.write(w_pipe, b'1')
|
||||
|
||||
screen = Screen(None, rows, columns, scrollback, cell_width, cell_height, 0, ToChild())
|
||||
|
||||
def parse_bytes(data: bytes) -> None:
|
||||
data = memoryview(data)
|
||||
while data:
|
||||
dest = screen.test_create_write_buffer()
|
||||
s = screen.test_commit_write_buffer(data, dest)
|
||||
data = data[s:]
|
||||
screen.test_parse_written_data()
|
||||
|
||||
|
||||
while True:
|
||||
rd, wd, _ = select.select([master_fd, r_pipe], [master_fd] if write_buf else [], [])
|
||||
if r_pipe in rd:
|
||||
os.read(r_pipe, 256)
|
||||
if master_fd in rd:
|
||||
try:
|
||||
data = os.read(master_fd, io.DEFAULT_BUFFER_SIZE)
|
||||
except OSError:
|
||||
data = b''
|
||||
if not data:
|
||||
break
|
||||
parse_bytes(data)
|
||||
if master_fd in wd:
|
||||
n = os.write(master_fd, write_buf)
|
||||
write_buf = write_buf[n:]
|
||||
if isatty:
|
||||
lines: list[str] = []
|
||||
screen.linebuf.as_ansi(lines.append)
|
||||
sys.stdout.write(''.join(lines))
|
||||
else:
|
||||
sys.stdout.write(str(screen.linebuf))
|
||||
|
||||
|
||||
def main() -> None:
|
||||
run_parsing_benchmark()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -21,12 +21,10 @@ import (
|
||||
|
||||
const (
|
||||
folder = "dependencies"
|
||||
fonts_folder = "fonts"
|
||||
macos_prefix = "/Users/Shared/kitty-build/sw/sw"
|
||||
macos_python = "python/Python.framework/Versions/Current/bin/python3"
|
||||
macos_python_framework = "python/Python.framework/Versions/Current/Python"
|
||||
macos_python_framework_exe = "python/Python.framework/Versions/Current/Resources/Python.app/Contents/MacOS/Python"
|
||||
NERD_URL = "https://github.com/ryanoasis/nerd-fonts/releases/latest/download/NerdFontsSymbolsOnly.tar.xz"
|
||||
)
|
||||
|
||||
func root_dir() string {
|
||||
@@ -37,14 +35,6 @@ func root_dir() string {
|
||||
return f
|
||||
}
|
||||
|
||||
func fonts_dir() string {
|
||||
f, e := filepath.Abs(fonts_folder)
|
||||
if e != nil {
|
||||
exit(e)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
func exit(x any) {
|
||||
@@ -335,19 +325,6 @@ func dependencies(args []string) {
|
||||
}); err != nil {
|
||||
exit(err)
|
||||
}
|
||||
tarfile, _ = filepath.Abs(cached_download(NERD_URL))
|
||||
root = fonts_dir()
|
||||
if err := os.MkdirAll(root, 0o755); err != nil {
|
||||
exit(err)
|
||||
}
|
||||
cmd = exec.Command("tar", "xf", tarfile, "SymbolsNerdFontMono-Regular.ttf")
|
||||
cmd.Dir = root
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err = cmd.Run(); err != nil {
|
||||
exit(err)
|
||||
}
|
||||
|
||||
fmt.Println(`Dependencies downloaded. Now build kitty with: ./dev.sh build`)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,12 +10,16 @@ import subprocess
|
||||
import tarfile
|
||||
import time
|
||||
|
||||
from bypy.constants import OUTPUT_DIR, PREFIX, python_major_minor_version
|
||||
from bypy.constants import OUTPUT_DIR, PREFIX, is64bit, python_major_minor_version
|
||||
from bypy.freeze import extract_extension_modules, freeze_python, path_to_freeze_dir
|
||||
from bypy.utils import get_dll_path, mkdtemp, py_compile, walk
|
||||
|
||||
j = os.path.join
|
||||
machine = (os.uname()[4] or '').lower()
|
||||
if machine.startswith('arm64') or machine.startswith('aarch64'):
|
||||
arch = 'arm64'
|
||||
else:
|
||||
arch = 'x86_64' if is64bit else 'i686'
|
||||
self_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
py_ver = '.'.join(map(str, python_major_minor_version()))
|
||||
iv = globals()['init_env']
|
||||
@@ -194,13 +198,12 @@ def strip_binaries(files):
|
||||
def create_tarfile(env, compression_level='9'):
|
||||
print('Creating archive...')
|
||||
base = OUTPUT_DIR
|
||||
arch = 'arm64' if 'arm64' in os.environ['BYPY_ARCH'] else ('i686' if 'i386' in os.environ['BYPY_ARCH'] else 'x86_64')
|
||||
try:
|
||||
shutil.rmtree(base)
|
||||
except OSError as err:
|
||||
if err.errno not in (errno.ENOENT, errno.EBUSY): # EBUSY when the directory is mountpoint
|
||||
if err.errno != errno.ENOENT:
|
||||
raise
|
||||
os.makedirs(base, exist_ok=True)
|
||||
os.mkdir(base)
|
||||
dist = os.path.join(base, f'{kitty_constants["appname"]}-{kitty_constants["version"]}-{arch}.tar')
|
||||
with tarfile.open(dist, mode='w', format=tarfile.PAX_FORMAT) as tf:
|
||||
cwd = os.getcwd()
|
||||
@@ -213,8 +216,7 @@ def create_tarfile(env, compression_level='9'):
|
||||
print('Compressing archive...')
|
||||
ans = f'{dist.rpartition(".")[0]}.txz'
|
||||
start_time = time.time()
|
||||
threads = 4 if arch == 'i686' else 0
|
||||
subprocess.check_call(['xz', '--verbose', f'--threads={threads}', '-f', f'-{compression_level}', dist])
|
||||
subprocess.check_call(['xz', '--verbose', '--threads=0', '-f', f'-{compression_level}', dist])
|
||||
secs = time.time() - start_time
|
||||
print('Compressed in {} minutes {} seconds'.format(secs // 60, secs % 60))
|
||||
os.rename(f'{dist}.xz', ans)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Requires installation of XCode >= 10.3 and go 1.23 and Python 3 and
|
||||
# Requires installation of XCode 10.3 and go 1.19 and Python 3 and
|
||||
# python3 -m pip install certifi
|
||||
|
||||
vm_name 'macos-kitty'
|
||||
root '/Users/Shared/kitty-build'
|
||||
python '/usr/local/bin/python3'
|
||||
universal 'true'
|
||||
deploy_target '11.0'
|
||||
deploy_target '10.14'
|
||||
|
||||
@@ -130,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):
|
||||
@@ -441,7 +441,7 @@ class Freeze(object):
|
||||
py_compile(join(self.resources_dir, 'Python'))
|
||||
|
||||
@flush
|
||||
def makedmg(self, d, volname, format='ULMO'):
|
||||
def makedmg(self, d, volname, format='ULFO'):
|
||||
''' Copy a directory d into a dmg named volname '''
|
||||
print('\nMaking dmg...')
|
||||
sys.stdout.flush()
|
||||
|
||||
@@ -40,7 +40,7 @@ Action Shortcut
|
||||
======================== =======================
|
||||
New tab :sc:`new_tab` (also :kbd:`⌘+t` on macOS)
|
||||
Close tab :sc:`close_tab` (also :kbd:`⌘+w` on macOS)
|
||||
Next tab :sc:`next_tab` (also :kbd:`⇧+⌃+⇥` and :kbd:`⇧+⌘+]` on macOS)
|
||||
Next tab :sc:`next_tab` (also :kbd:`⌃+⇥` and :kbd:`⇧+⌘+]` on macOS)
|
||||
Previous tab :sc:`previous_tab` (also :kbd:`⇧+⌃+⇥` and :kbd:`⇧+⌘+[` on macOS)
|
||||
Next layout :sc:`next_layout`
|
||||
Move tab forward :sc:`move_tab_forward`
|
||||
|
||||
@@ -99,12 +99,12 @@ Customizing the installation
|
||||
_kitty_install_cmd \
|
||||
installer=nightly dest=/some/other/location
|
||||
|
||||
* You can specify a specific version to install, with:
|
||||
* You can specify a different install location, with ``dest``:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
_kitty_install_cmd \
|
||||
installer=version-0.35.2
|
||||
dest=/some/other/location
|
||||
|
||||
* You can tell the installer not to launch |kitty| after installing it with
|
||||
``launch=n``:
|
||||
|
||||
@@ -23,10 +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 mostly X11 and DBUS on Linux, as can be seen in the `linux-dev
|
||||
<https://github.com/kovidgoyal/kitty/blob/master/.github/workflows/ci.yml>`__
|
||||
CI job.
|
||||
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.
|
||||
@@ -103,7 +100,6 @@ Build-time dependencies:
|
||||
* ``simde``
|
||||
* ``go`` >= _build_go_version (see :file:`go.mod` for go packages used during building)
|
||||
* ``pkg-config``
|
||||
* Symbols NERD Font Mono either installed system-wide or placed in :file:`fonts/SymbolsNerdFontMono-Regular.ttf`
|
||||
* For building on Linux in addition to the above dependencies you might also
|
||||
need to install the following packages, if they are not already installed by
|
||||
your distro:
|
||||
|
||||
@@ -9,40 +9,6 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
|
||||
Recent major new features
|
||||
---------------------------
|
||||
|
||||
Cursor trails [0.37]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Show an animated trail when the text cursor makes large jumps making it easy
|
||||
to follow cursor movements. Inspired by the similar feature in neovide, but
|
||||
works with terminal multiplexers and kitty windows as well. See :pull:`the pull
|
||||
request <7970>` for a demonstration video. This feature is optional and must be
|
||||
turned on by the :opt:`cursor_trail` option in :file:`kitty.conf`.
|
||||
|
||||
|
||||
Variable font support [0.36]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Terminal aficionados spend all day staring at text, so getting text
|
||||
rendering just right is very important. In that spirit, kitty now supports
|
||||
`OpenType Variable fonts <https://en.wikipedia.org/wiki/Variable_font>`__.
|
||||
These allow precise customisation of font characteristics, such as weight and
|
||||
spacing. Not only that, kitty now has a new :doc:`choose-fonts
|
||||
<kittens/choose-fonts>` kitten that provides a UI for choosing fonts with
|
||||
support for font features, variable fonts and previews of how the font will
|
||||
look. This is in addition to its existing best-in-class font customization
|
||||
abilities, such as: :opt:`symbol_map`, :opt:`text_composition_strategy`,
|
||||
:opt:`font_features` and :opt:`modify_font`. kitty knows text rendering is
|
||||
important, and goes the extra mile for it.
|
||||
|
||||
Desktop notifications [0.36]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|kitty| now has a :doc:`notify </kittens/notify>` kitten that can be used to
|
||||
display desktop notifications from the command line, even over SSH. It has
|
||||
support for icons, buttons, updating notifications, waiting till
|
||||
the notification is closed, etc. The underlying :doc:`desktop-notifications`
|
||||
protocol has been expanded to support all these features.
|
||||
|
||||
Wayland goodies [0.34]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -84,207 +50,6 @@ consumption to do the same tasks.
|
||||
Detailed list of changes
|
||||
-------------------------------------
|
||||
|
||||
0.38.1 [2024-12-26]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- macOS: Fix a regression in the previous release that broke rendering of Emoji using the VS16 variation selector (:iss:`8130`)
|
||||
|
||||
- When automatically changing colors based on OS color preference, first reset
|
||||
all colors to default before applying the new theme so that even colors not
|
||||
specified in the theme are correct (:iss:`8124`)
|
||||
|
||||
- Graphics: Fix deleted but not freed images without any placements being incorrectly freed on a subsequent delete command (:disc:`8129`)
|
||||
|
||||
- Graphics: Fix deletion of images by id not working for images with no placements (:disc:`8129`)
|
||||
|
||||
- Add support for `escape code protocol <https://github.com/contour-terminal/contour/blob/master/docs/vt-extensions/color-palette-update-notifications.md>`__ for notifying applications on dark/light color scheme change
|
||||
|
||||
- Cursor trails: Fix pure vertical movement sometimes not triggering a trail and holding down a key in nvim causing the trail to be glitchy (:pull:`8152`, :pull:`8153`)
|
||||
|
||||
- macOS: Fix mouse cursor shape not always being reset to text cursor when mouse re-enters kitty (:iss:`8155`)
|
||||
|
||||
- clone-in-kitty: Fix :envvar:`KITTY_WINDOW_ID` being cloned and thus having incorrect value (:iss:`8161`)
|
||||
|
||||
|
||||
0.38.0 [2024-12-15]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Allow :ref:`specifying individual color themes <auto_color_scheme>` to use so that kitty changes colors automatically following the OS dark/light mode
|
||||
|
||||
- :opt:`notify_on_cmd_finish`: Automatically remove notifications when the window gains focus or the next notification is shown. Clearing behavior can be configured (:pull:`8100`)
|
||||
|
||||
- Discard OSC 9 notifications that start with :code:`4;` because some misguided software is using it for "progress reporting" (:iss:`8011`)
|
||||
|
||||
- Wayland GNOME: Workaround bug in mutter causing double tap on titlebar to not always work (:iss:`8054`)
|
||||
|
||||
- clipboard kitten: Fix a bug causing kitten to hang in filter mode when input data size is not divisible by 3 and larger than 8KB (:iss:`8059`)
|
||||
|
||||
- Wayland: Fix an abort when a client program tries to set an invalid title containing interleaved escape codes and UTF-8 multi-byte characters (:iss:`8067`)
|
||||
|
||||
- Graphics protocol: Fix delete by number not deleting newest image with the specified number (:iss:`8071`)
|
||||
|
||||
- Fix dashed and dotted underlines not being drawn at the same y position as straight underlines at all font sizes (:iss:`8074`)
|
||||
|
||||
- panel kitten: Allow creating floating and on-top panels with arbitrary placement and size on Wayland (:pull:`8068`)
|
||||
|
||||
- :opt:`remote_control_password`: Fix using a password without any actions not working (:iss:`8082`)
|
||||
|
||||
- Fix enlarging window when a long line is wrapped between the first line of the scrollback buffer and the screen inserting a spurious newline (:iss:`7033`)
|
||||
|
||||
- When re-attaching a detached tab preserve internal layout state such as biases and orientations (:iss:`8106`)
|
||||
|
||||
- hints/unicode_input kittens: Do not lose keypresses that are sent very rapidly via an automation tool immediately after the kitten is launched (:iss:`7089`)
|
||||
|
||||
|
||||
0.37.0 [2024-10-30]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- A new optional :opt:`text cursor movement animation <cursor_trail>` that
|
||||
shows a "trail" following the movement of the cursor making it easy to follow
|
||||
large cursor jumps (:pull:`7970`)
|
||||
|
||||
- Custom kittens: Add :ref:`a framework <kitten_main_rc>` for easily and securely using remote control from within a kitten's :code:`main()` function
|
||||
|
||||
- kitten icat: Fix the :option:`kitty +kitten icat --no-trailing-newline` not working when using unicode placeholders (:iss:`7948`)
|
||||
|
||||
- :opt:`tab_title_template` allow using the 256 terminal colors for formatting (:disc:`7976`)
|
||||
|
||||
- Fix resizing window when alternate screen is active does not preserve trailing blank output line in the main screen (:iss:`7978`)
|
||||
|
||||
- Wayland: Fix :opt:`background_opacity` less than one causing flicker on startup when the Wayland compositor supports single pixel buffers (:iss:`7987`)
|
||||
|
||||
- Fix background image flashing when closing a tab (:iss:`7999`)
|
||||
|
||||
- When running a kitten that modifies the kitty config file if no config file exists create a commented out default config file and then modify it (:iss:`7991`)
|
||||
|
||||
0.36.4 [2024-09-27]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Fix a regression in the previous release that caused window padding to be rendered opaque even when :opt:`background_opacity` is less than 1 (:iss:`7895`)
|
||||
|
||||
- Wayland GNOME: Fix a crash when using multiple monitors with different scales and starting on or moving to the monitor with lower scale (:iss:`7894`)
|
||||
|
||||
- macOS: Fix a regression in the previous release that caused junk to be rendered in font previews in the choose fonts kitten and crash on Intel macs (:iss:`7892`)
|
||||
|
||||
|
||||
0.36.3 [2024-09-25]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- The option ``second_transparent_bg`` has been removed and replaced by :opt:`transparent_background_colors` which allows setting up to seven additional colors that will be transparent, with individual opacities per color (:iss:`7646`)
|
||||
|
||||
- Fix a regression in the previous release that broke use of the ``cd`` command in session files (:iss:`7829`)
|
||||
|
||||
- macOS: Fix shortcuts that become entries in the global menubar being reported as removed shortcuts in the debug output
|
||||
|
||||
- macOS: Fix :opt:`macos_option_as_alt` not working when :kbd:`caps lock` is engaged (:iss:`7836`)
|
||||
|
||||
- Fix a regression when tinting of background images was introduced that caused window borders to have :opt:`background_opacity` applied to them (:iss:`7850`)
|
||||
|
||||
- Fix a regression that broke writing to the clipboard using the OSC 5522 protocol (:iss:`7858`)
|
||||
|
||||
- macOS: Fix a regression in the previous release that caused kitty to fail to run after an unclean shutdown/crash when using --single-instance (:iss:`7846`)
|
||||
|
||||
- kitten @ ls: Fix the ``--self`` flag not working (:iss:`7864`)
|
||||
|
||||
- Remote control: Fix ``--match state:self`` not working (:disc:`7886`)
|
||||
|
||||
- Splits layout: Allow setting the ``split_axis`` option to ``auto`` so that all new windows have their split axis chosen automatically unless explicitly specified in the launch command (:iss:`7887`)
|
||||
|
||||
0.36.2 [2024-09-06]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Linux: Fix a regression in 0.36.0 that caused font features defined via fontconfig to be ignored (:iss:`7773`)
|
||||
|
||||
- :ac:`goto_tab`: Allow numbers less than ``-1`` to go to the Nth previously active tab
|
||||
|
||||
- Wayland: Fix for upcoming explicit sync changes in Wayland compositors breaking kitty (:iss:`7767`)
|
||||
|
||||
- Remote control: When listening on a UNIX domain socket only allow connections from processes having the same user id (:pull:`7777`)
|
||||
|
||||
- kitten @: Fix a regression connecting to TCP sockets using plain IP addresses rather than hostnames (:iss:`7794`)
|
||||
|
||||
- diff kitten: Fix a regression that broke diffing against remote files (:iss:`7797`)
|
||||
|
||||
0.36.1 [2024-08-24]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Allow specifying that the :opt:`cursor shape for unfocused windows <cursor_shape_unfocused>` should remain unchanged (:pull:`7728`)
|
||||
|
||||
- MacOS Intel: Fix a crash in the choose-fonts kitten when displaying previews of variable fonts (:iss:`7734`)
|
||||
|
||||
- Remote control: Fix a regression causing an escape code to leak when using @ launch with ``--no-response`` over the TTY (:iss:`7752`)
|
||||
|
||||
- OSC 52: Fix a regression in the previous release that broke handling of invalid base64 encoded data in OSC 52 requests (:iss:`7757`)
|
||||
|
||||
- macOS: Fix a regression in the previous release that caused :option:`kitty --single-instance` to not work when using :file:`macos-launch-services-cmdline`
|
||||
|
||||
0.36.0 [2024-08-17]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Support `OpenType Variable fonts <https://en.wikipedia.org/wiki/Variable_font>`__ (:iss:`3711`)
|
||||
|
||||
- A new :doc:`choose-fonts </kittens/choose-fonts>` kitten that provides a UI with font previews to ease selection of fonts. Also has support for font features and variable fonts
|
||||
|
||||
- Allow animating the blinking of the cursor. See :opt:`cursor_blink_interval` for how to configure it
|
||||
|
||||
- Add NERD fonts builtin so that users don't have to install them to use NERD symbols in kitty. The builtin font is used only if the symbols are not available in some system font
|
||||
|
||||
- launch command: A new :option:`launch --bias` option to adjust the size of newly created windows declaratively (:iss:`7634`)
|
||||
|
||||
- A new option :opt:`transparent_background_colors` to make a second background color semi-transparent via :opt:`background_opacity`. Useful for things like cursor line highlight in editors (:iss:`7646`)
|
||||
|
||||
- A new :doc:`notify </kittens/notify>` kitten to show desktop notifications
|
||||
from the command line with support for icons, buttons and more.
|
||||
|
||||
- Desktop notifications protocol: Add support for icons, buttons, closing of notifications, expiry of notifications, updating of notifications and querying if the terminal emulator supports the protocol (:iss:`7657`, :iss:`7658`, :iss:`7659`)
|
||||
|
||||
- A new option :opt:`filter_notification` to filter out or perform arbitrary actions on desktop notifications based on sophisticated criteria (:iss:`7670`)
|
||||
|
||||
- A new protocol to allow terminal applications to change colors in the terminal more robustly than with the legacy XTerm protocol (:ref:`color_control`)
|
||||
|
||||
- Sessions: A new command ``focus_matching_window`` to shift focus to a specific window, useful when creating complex layouts with splits (:disc:`7635`)
|
||||
|
||||
- Speed up loading of large background images by caching the decoded image data. Also allow using images in JPEG/WEBP/TIFF/GIF/BMP formats in addition to PNG
|
||||
|
||||
- Wayland: Allow fractional scales less than one (:pull:`7549`)
|
||||
|
||||
- Wayland: Fix specifying the output name for the panel kitten not working (:iss:`7573`)
|
||||
|
||||
- icat kitten: Add an option :option:`kitty +kitten icat --no-trailing-newline` to leave the cursor to the right of the image (:iss:`7574`)
|
||||
|
||||
- Speed up ``kitty --version`` and ``kitty --single-instance`` (for all subsequent instances). They are now the fastest of all terminal emulators with similar functionality
|
||||
|
||||
- macOS: Fix rendering of the unicode hyphen (U+2010) character when using a font that does not include a glyph for it (:iss:`7525`)
|
||||
|
||||
- macOS 15: Handle Fn modifier when detecting global shortcuts (:iss:`7582`)
|
||||
|
||||
- Dispatch any clicks waiting for :opt:`click_interval` on key events (:iss:`7601`)
|
||||
|
||||
- ``kitten run-shell``: Automatically add the directory containing the kitten binary to PATH if needed. Controlled via the ``--inject-self-onto-path`` option (`disc`:7668`)
|
||||
|
||||
- Wayland: Fix an issue with mouse selections not being stopped when there are multiple OS windows (:iss:`7381`)
|
||||
|
||||
- Splits layout: Fix the ``move_to_screen_edge`` action breaking when only a single window is present (:iss:`7621`)
|
||||
|
||||
- Add support for in-band window resize notifications (:iss:`7642`)
|
||||
|
||||
- Allow controlling the easing curves used for :opt:`visual_bell_duration`
|
||||
|
||||
- New special rendering for font symbols useful in drawing commit graphs (:pull:`7681`)
|
||||
|
||||
- diff kitten: Add bindings to jump to next and previous file (:pull:`7683`)
|
||||
|
||||
- Wayland GNOME: Fix the font size in the OS Window title bar changing with the size of the text in the window (:disc:`7677`)
|
||||
|
||||
- Wayland GNOME: Fix a small rendering artifact when docking a window at a screen edge or maximizing it (:iss:`7701`)
|
||||
|
||||
- When :opt:`shell` is set to ``.`` respect the SHELL environment variable in the environment in which kitty is launched (:pull:`7714`)
|
||||
|
||||
- macOS: Bump the minimum required macOS version to Catalina released five years ago.
|
||||
|
||||
- Fix a regression in :opt:`notify_on_cmd_finish` that caused notifications to appear for every command after the first (:iss:`7725`)
|
||||
|
||||
|
||||
0.35.2 [2024-06-22]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
Color control
|
||||
====================
|
||||
|
||||
Saving and restoring colors
|
||||
------------------------------
|
||||
==============================
|
||||
|
||||
It is often useful for a full screen application with its own color themes to
|
||||
set the default foreground, background, selection and cursor colors and the ANSI
|
||||
@@ -27,157 +24,3 @@ foreground, selection background, selection foreground and cursor color and the
|
||||
promoting interoperability, kitty added support for xterm's escape codes as
|
||||
well, and changed this extension to also save/restore the entire ANSI color
|
||||
table.
|
||||
|
||||
.. _color_control:
|
||||
|
||||
Setting and querying colors
|
||||
-------------------------------
|
||||
|
||||
While there exists a legacy protocol developed by XTerm for querying and
|
||||
setting colors, as with most XTerm protocols it suffers from the usual design
|
||||
limitations of being under specified and in-sufficient. XTerm implements
|
||||
querying of colors using OSC 4,5,6,10-19,104,105,106,110-119. This absurd
|
||||
profusion of numbers is completely unnecessary, redundant and requires adding
|
||||
two new numbers for every new color. Also XTerm's protocol doesn't handle the
|
||||
case of colors that are unknown to the terminal or that are not a set value,
|
||||
for example, many terminals implement selection as a reverse video effect not a
|
||||
fixed color. The XTerm protocol has no way to query for this condition. The
|
||||
protocol also doesn't actually specify the format in which colors are reported,
|
||||
deferring to a man page for X11!
|
||||
|
||||
Instead kitty has developed a single number based protocol that addresses all
|
||||
these shortcomings and is future proof by virtue of using string keys rather
|
||||
than numbers. The syntax of the escape code is::
|
||||
|
||||
<OSC> 21 ; key=value ; key=value ; ... <ST>
|
||||
|
||||
The spaces in the above definition are for reading clarity and should be ignored.
|
||||
Here, ``<OSC>`` is the two bytes ``0x1b (ESC)`` and ``0x5d (])``. ``ST`` is
|
||||
either ``0x7 (BEL)`` or the two bytes ``0x1b (ESC)`` and ``0x5c (\\)``.
|
||||
|
||||
``key`` is a number from 0-255 to query or set the color values from the
|
||||
terminals ANSI color table, or one of the strings in the table below for
|
||||
special colors:
|
||||
|
||||
================================= =============================================== ===============================
|
||||
key meaning dynamic
|
||||
================================= =============================================== ===============================
|
||||
foreground The default foreground text color Not applicable
|
||||
background The default background text color Not applicable
|
||||
selection_background The background color of selections Reverse video
|
||||
selection_foreground The foreground color of selections Reverse video
|
||||
cursor The color of the text cursor Foreground color
|
||||
cursor_text The color of text under the cursor Background color
|
||||
visual_bell The color of a visual bell Automatic color selection based on current screen colors
|
||||
transparent_background_color1..8 A background color that is rendered Unset
|
||||
with the specified opacity in cells that have
|
||||
the specified background color. An opacity
|
||||
value less than zero means, use the
|
||||
:opt:`background_opacity` value.
|
||||
================================= =============================================== ===============================
|
||||
|
||||
In this table the third column shows what effect setting the color to *dynamic*
|
||||
has in kitty and many other terminal emulators. It is advisory only, terminal
|
||||
emulators may not support dynamic colors for these or they may have other
|
||||
effects. Setting the ANSI color table colors to dynamic is not allowed.
|
||||
|
||||
Querying current color values
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To query colors values, the client program sends this escape code with the
|
||||
``value`` field set to ``?`` (the byte ``0x3f``). The terminal then responds
|
||||
with the same escape code, but with the ``?`` replaced by the :ref:`encoded
|
||||
color value <color_control_color_encoding>`. If the queried color is one that
|
||||
does not have a defined value, for example, if the terminal is using a reverse
|
||||
video effect or a gradient or similar, then the value must be empty, that is
|
||||
the response contains only the key and ``=``, no value. For example, if the
|
||||
client sends::
|
||||
|
||||
<OSC> 21 ; foreground=? ; cursor=? <ST>
|
||||
|
||||
The terminal responds::
|
||||
|
||||
<OSC> 21 ; foreground=rgb:ff/00/00 ; cursor= <ST>
|
||||
|
||||
This indicates that the foreground color is red and the cursor color is
|
||||
undefined (typically the cursor takes the color of the text under it and the
|
||||
text takes the color of the background).
|
||||
|
||||
If the terminal does not know a field that a client send to it for a query it
|
||||
must respond back with the ``field=?``, that is, it must send back a question
|
||||
mark as the value.
|
||||
|
||||
|
||||
Setting color values
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To set a color value, the client program sends this escape code with the
|
||||
``value`` field set to either an :ref:`encoded color value
|
||||
<color_control_color_encoding>` or the empty value. The empty value means
|
||||
the terminal should use a dynamic color for example reverse video for
|
||||
selections or similar. To reset a color to its default value (i.e. the value it
|
||||
would have if it was never set) the client program should send just the key
|
||||
name with no ``=`` and no value. For example::
|
||||
|
||||
<OSC> 21 ; foreground=green ; cursor= ; background <ST>
|
||||
|
||||
This sets the foreground to the color green, sets the cursor color to dynamic
|
||||
(usually meaning the cursor takes the color of the text under it) and resets
|
||||
the background color to its default value.
|
||||
|
||||
To check if setting succeeded, the client can simply query the color, in fact
|
||||
the two can be combined into a single escape code, for example::
|
||||
|
||||
<OSC> 21 ; foreground=white ; foreground=? <ST>
|
||||
|
||||
The terminal will change the foreground color and reply with the new foreground
|
||||
color.
|
||||
|
||||
|
||||
.. _color_control_color_encoding:
|
||||
|
||||
Color value encoding
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The color encoding is inherited from the scheme used by XTerm, for
|
||||
compatibility, but a sane, rigorously specified subset is chosen.
|
||||
|
||||
RGB colors are encoded in one of three forms:
|
||||
|
||||
``rgb:<red>/<green>/<blue>``
|
||||
| <red>, <green>, <blue> := h | hh | hhh | hhhh
|
||||
| h := single hexadecimal digits (case insignificant)
|
||||
| Note that h indicates the value scaled in 4 bits, hh the value scaled in 8 bits,
|
||||
hhh the value scaled in 12 bits, and hhhh the value scaled in 16 bits, respectively.
|
||||
|
||||
``#<h...>``
|
||||
| h := single hexadecimal digits (case insignificant)
|
||||
| #RGB (4 bits each)
|
||||
| #RRGGBB (8 bits each)
|
||||
| #RRRGGGBBB (12 bits each)
|
||||
| #RRRRGGGGBBBB (16 bits each)
|
||||
| The R, G, and B represent single hexadecimal digits. When fewer than 16 bits
|
||||
each are specified, they represent the most significant bits of the value
|
||||
(unlike the “rgb:” syntax, in which values are scaled). For example,
|
||||
the string ``#3a7`` is the same as ``#3000a0007000``.
|
||||
|
||||
``rgbi:<red>/<green>/<blue>``
|
||||
red, green, and blue are floating-point values between 0.0 and 1.0, inclusive. The input format for these values is an optional
|
||||
sign, a string of numbers possibly containing a decimal point, and an optional exponent field containing an E or e followed by a possibly
|
||||
signed integer string. Values outside the ``0 - 1`` range must be clipped to be within the range.
|
||||
|
||||
If a color should have an alpha component, it must be suffixed to the color
|
||||
specification in the form :code:`@number between zero and one`. For example::
|
||||
|
||||
red@0.5 rgb:ff0000@0.1 #ff0000@0.3
|
||||
|
||||
The syntax for the floating point alpha component is the same as used for the
|
||||
components of ``rgbi`` defined above. When not specified, the default alpha
|
||||
value is ``1.0``. Values outside the range ``0 - 1`` must be clipped
|
||||
to be within the range, negative values may have special context dependent
|
||||
meaning.
|
||||
|
||||
In addition, the following color names are accepted (case-insensitively) corresponding to the
|
||||
specified RGB values.
|
||||
|
||||
.. include:: generated/color-names.rst
|
||||
|
||||
29
docs/conf.py
29
docs/conf.py
@@ -282,29 +282,13 @@ if you specify a program-to-run you can use the special placeholder
|
||||
p(f'\nThe source code for this kitten is `available on GitHub <{scurl}>`_.')
|
||||
p('\nCommand Line Interface')
|
||||
p('-' * 72)
|
||||
appname = f'kitten {kitten}'
|
||||
if kitten in ('panel', 'broadcast', 'remote_file'):
|
||||
appname = 'kitty +' + appname
|
||||
p('\n\n' + option_spec_as_rst(
|
||||
data['options'], message=data['help_text'], usage=data['usage'], appname=appname, heading_char='^'))
|
||||
data['options'], message=data['help_text'], usage=data['usage'], appname=f'kitty +kitten {kitten}',
|
||||
heading_char='^'))
|
||||
|
||||
# }}}
|
||||
|
||||
|
||||
def write_color_names_table() -> None: # {{{
|
||||
from kitty.rgb import color_names
|
||||
def s(c: Any) -> str:
|
||||
return f'{c.red:02x}/{c.green:02x}/{c.blue:02x}'
|
||||
with open('generated/color-names.rst', 'w') as f:
|
||||
p = partial(print, file=f)
|
||||
p('=' * 50, '=' * 20)
|
||||
p('Name'.ljust(50), 'RGB value')
|
||||
p('=' * 50, '=' * 20)
|
||||
for name, col in color_names.items():
|
||||
p(name.ljust(50), s(col))
|
||||
p('=' * 50, '=' * 20)
|
||||
# }}}
|
||||
|
||||
def write_remote_control_protocol_docs() -> None: # {{{
|
||||
from kitty.rc.base import RemoteCommand, all_command_names, command_for_name
|
||||
field_pat = re.compile(r'\s*([^:]+?)\s*:\s*(.+)')
|
||||
@@ -333,10 +317,8 @@ def write_remote_control_protocol_docs() -> None: # {{{
|
||||
else:
|
||||
title = f'{title} (optional)'
|
||||
p(f':code:`{title}`')
|
||||
p(' ', desc)
|
||||
p()
|
||||
p()
|
||||
p()
|
||||
p(' ', desc), p()
|
||||
p(), p()
|
||||
|
||||
with open('generated/rc.rst', 'w') as f:
|
||||
p = partial(print, file=f)
|
||||
@@ -719,7 +701,7 @@ def setup_man_pages() -> None:
|
||||
base = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
for x in glob.glob(os.path.join(base, 'docs/kittens/*.rst')):
|
||||
kn = os.path.basename(x).rpartition('.')[0]
|
||||
if kn in ('custom', 'developing-builtin-kittens'):
|
||||
if kn == 'custom':
|
||||
continue
|
||||
cd = get_kitten_cli_docs(kn) or {}
|
||||
khn = kn.replace('_', '-')
|
||||
@@ -766,7 +748,6 @@ def setup(app: Any) -> None:
|
||||
kn = all_kitten_names()
|
||||
write_cli_docs(kn)
|
||||
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)
|
||||
|
||||
@@ -33,16 +33,7 @@ notification from a shell script::
|
||||
To show a message with a title and a body::
|
||||
|
||||
printf '\x1b]99;i=1:d=0;Hello world\x1b\\'
|
||||
printf '\x1b]99;i=1:p=body;This is cool\x1b\\'
|
||||
|
||||
.. tip::
|
||||
|
||||
|kitty| also comes with its own :doc:`statically compiled command line tool </kittens/notify>` to easily display
|
||||
notifications, with all their advanced features. For example:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
kitten notify "Hello world" A good day to you
|
||||
printf '\x1b]99;i=1:d=1:p=body;This is cool\x1b\\'
|
||||
|
||||
The most important key in the metadata is the ``p`` key, it controls how the
|
||||
payload is interpreted. A value of ``title`` means the payload is setting the
|
||||
@@ -52,55 +43,20 @@ and so on, see the table below for full details.
|
||||
The design of the escape code is fundamentally chunked, this is because
|
||||
different terminal emulators have different limits on how large a single escape
|
||||
code can be. Chunking is accomplished by the ``i`` and ``d`` keys. The ``i``
|
||||
key is the *notification id* which is an :ref:`identifier`.
|
||||
The ``d`` key stands for *done* and can only take the
|
||||
key is the *notification id* which can be any string containing the characters
|
||||
``[a-zA-Z0-9_-+.]``. The ``d`` key stands for *done* and can only take the
|
||||
values ``0`` and ``1``. A value of ``0`` means the notification is not yet done
|
||||
and the terminal emulator should hold off displaying it. A non-zero value means
|
||||
and the terminal emulator should hold off displaying it. A value of ``1`` means
|
||||
the notification is done, and should be displayed. You can specify the title or
|
||||
body multiple times and the terminal emulator will concatenate them, thereby
|
||||
allowing arbitrarily long text (terminal emulators are free to impose a sensible
|
||||
limit to avoid Denial-of-Service attacks). The size of the payload must be no
|
||||
longer than ``2048`` bytes, *before being encoded* or ``4096`` encoded bytes.
|
||||
longer than ``2048`` bytes, *before being encoded*.
|
||||
|
||||
Both the ``title`` and ``body`` payloads must be either :ref:`safe_utf8` text
|
||||
or UTF-8 text that is :ref:`base64` encoded, in which case there must be an
|
||||
``e=1`` key in the metadata to indicate the payload is :ref:`base64`
|
||||
encoded. No HTML or other markup in the plain text is allowed. It is strictly
|
||||
plain text, to be interpreted as such.
|
||||
|
||||
Allowing users to filter notifications
|
||||
-------------------------------------------------------
|
||||
|
||||
.. versionadded:: 0.36.0
|
||||
Specifying application name and notification type
|
||||
|
||||
Well behaved applications should identify themselves to the terminal
|
||||
by means of two keys ``f`` which is the application name and ``t``
|
||||
which is the notification type. These are free form keys, they can contain
|
||||
any values, their purpose is to allow users to easily filter out
|
||||
notifications they do not want. Both keys must have :ref:`base64`
|
||||
encoded UTF-8 text as their values. The ``t`` key can be specified multiple
|
||||
times, as notifications can have more than one type. See the `freedesktop.org
|
||||
spec
|
||||
<https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html#categories>`__
|
||||
for examples of notification types.
|
||||
|
||||
.. note::
|
||||
The application name should generally be set to the filename of the
|
||||
applications `desktop file
|
||||
<https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#file-naming>`__
|
||||
(without the ``.desktop`` part) or the bundle identifier for a macOS
|
||||
application. While not strictly necessary, this allows the terminal
|
||||
emulator to deduce an icon for the notification when one is not specified.
|
||||
|
||||
.. tip::
|
||||
|
||||
|kitty| has sophisticated notification filtering and management
|
||||
capabilities via :opt:`filter_notification`.
|
||||
|
||||
|
||||
Being informed when user activates the notification
|
||||
-------------------------------------------------------
|
||||
Both the ``title`` and ``body`` payloads must be either UTF-8 encoded plain
|
||||
text with no embedded escape codes, or UTF-8 text that is Base64 encoded, in
|
||||
which case there must be an ``e=1`` key in the metadata to indicate the payload
|
||||
is Base64 encoded.
|
||||
|
||||
When the user clicks the notification, a couple of things can happen, the
|
||||
terminal emulator can focus the window from which the notification came, and/or
|
||||
@@ -115,319 +71,29 @@ escape code is::
|
||||
|
||||
The value of ``identifier`` comes from the ``i`` key in the escape code sent by
|
||||
the application. If the application sends no identifier, then the terminal
|
||||
*must* use ``i=0``. (Ideally ``i`` should have been left out from the response,
|
||||
but for backwards compatibility ``i=0`` is used). Actions can be preceded by a
|
||||
negative sign to turn them off, so for example if you do not want any action,
|
||||
turn off the default ``focus`` action with::
|
||||
*must* use ``i=0``. Actions can be preceded by a negative sign to turn them
|
||||
off, so for example if you do not want any action, turn off the default
|
||||
``focus`` action with::
|
||||
|
||||
a=-focus
|
||||
|
||||
Complete specification of all the metadata keys is in the :ref:`table below <keys_in_notificatons_protocol>`.
|
||||
If a terminal emulator encounters a key in the metadata it does not understand,
|
||||
Complete specification of all the metadata keys is in the table below. If a
|
||||
terminal emulator encounters a key in the metadata it does not understand,
|
||||
the key *must* be ignored, to allow for future extensibility of this escape
|
||||
code. Similarly if values for known keys are unknown, the terminal emulator
|
||||
*should* either ignore the entire escape code or perform a best guess effort to
|
||||
display it based on what it does understand.
|
||||
|
||||
|
||||
Being informed when a notification is closed
|
||||
------------------------------------------------
|
||||
|
||||
.. versionadded:: 0.36.0
|
||||
Notifications of close events
|
||||
|
||||
If you wish to be informed when a notification is closed, you can specify
|
||||
``c=1`` when sending the notification. For example::
|
||||
|
||||
<OSC> 99 ; i=mynotification : c=1 ; hello world <terminator>
|
||||
|
||||
Then, the terminal will send the following
|
||||
escape code to inform when the notification is closed::
|
||||
|
||||
<OSC> 99 ; i=mynotification : p=close ; <terminator>
|
||||
|
||||
If no notification id was specified ``i=0`` will be used in the response
|
||||
|
||||
If ``a=report`` is specified and the notification is activated/clicked on
|
||||
then both the activation report and close notification are sent. If the notification
|
||||
is updated then the close event is not sent unless the updated notification
|
||||
also requests a close notification.
|
||||
|
||||
Note that on some platforms, such as macOS, the OS does not inform applications
|
||||
when notifications are closed, on such platforms, terminals reply with::
|
||||
|
||||
<OSC> 99 ; i=mynotification : p=close ; untracked <terminator>
|
||||
|
||||
This means that the terminal has no way of knowing when the notification is
|
||||
closed. Instead, applications can poll the terminal to determine which
|
||||
notifications are still alive (not closed), with::
|
||||
|
||||
<OSC> 99 ; i=myid : p=alive ; <terminator>
|
||||
|
||||
The terminal will reply with::
|
||||
|
||||
<OSC> 99 ; i=myid : p=alive ; id1,id2,id3 <terminator>
|
||||
|
||||
Here, ``myid`` is present for multiplexer support. The response from the terminal
|
||||
contains a comma separated list of ids that are still alive.
|
||||
|
||||
|
||||
Updating or closing an existing notification
|
||||
----------------------------------------------
|
||||
|
||||
.. versionadded:: 0.36.0
|
||||
The ability to update and close a previous notification
|
||||
|
||||
To update a previous notification simply send a new notification with the same
|
||||
*notification id* (``i`` key) as the one you want to update. If the original
|
||||
notification is still displayed it will be replaced, otherwise a new
|
||||
notification is displayed. This can be used, for example, to show progress of
|
||||
an operation. How smoothly the existing notification is replaced
|
||||
depends on the underlying OS, for example, on Linux the replacement is usually flicker
|
||||
free, on macOS it isn't, because of Apple's design choices.
|
||||
Note that if no ``i`` key is specified, no updating must take place, even if
|
||||
there is a previous notification without an identifier. The terminal must
|
||||
treat these as being two unique *unidentified* notifications.
|
||||
|
||||
To close a previous notification, send::
|
||||
|
||||
<OSC> i=<notification id> : p=close ; <terminator>
|
||||
|
||||
This will close a previous notification with the specified id. If no such
|
||||
notification exists (perhaps because it was already closed or it was activated)
|
||||
then the request is ignored. If no ``i`` key is specified, this must be a no-op.
|
||||
|
||||
|
||||
Automatically expiring notifications
|
||||
-------------------------------------
|
||||
|
||||
A notification can be marked as expiring (being closed) automatically after
|
||||
a specified number of milliseconds using the ``w`` key. The default if
|
||||
unspecified is ``-1`` which means to use whatever expiry policy the OS has for
|
||||
notifications. A value of ``0`` means the notification should never expire.
|
||||
Values greater than zero specify the number of milliseconds after which the
|
||||
notification should be auto-closed. Note that the value of ``0``
|
||||
is best effort, some platforms honor it and some do not. Positive values
|
||||
are robust, since they can be implemented by the terminal emulator itself,
|
||||
by manually closing the notification after the expiry time. The notification
|
||||
could still be closed before the expiry time by user interaction or OS policy,
|
||||
but it is guaranteed to be closed once the expiry time has passed.
|
||||
|
||||
|
||||
Adding icons to notifications
|
||||
--------------------------------
|
||||
|
||||
.. versionadded:: 0.36.0
|
||||
Custom icons in notifications
|
||||
|
||||
Applications can specify a custom icon to be displayed with a notification.
|
||||
This can be the application's logo or a symbol such as error or warning
|
||||
symbols. The simplest way to specify an icon is by *name*, using the ``n``
|
||||
key. The value of this key is :ref:`base64` encoded UTF-8 text. Names
|
||||
can be either application names, or symbol names. The terminal emulator
|
||||
will try to resolve the name based on icons and applications available
|
||||
on the computer it is running on. The following list of well defined names
|
||||
must be supported by any terminal emulator implementing this spec.
|
||||
The ``n`` key can be specified multiple times, the terminal will go through
|
||||
the list in order and use the first icon that it finds available on the
|
||||
system.
|
||||
|
||||
.. table:: Universally available icon names
|
||||
|
||||
======================== ==============================================
|
||||
Name Description
|
||||
======================== ==============================================
|
||||
``error`` An error symbol
|
||||
``warn``, ``warning`` A warning symbol
|
||||
``info`` A symbol denoting an informational message
|
||||
``question`` A symbol denoting asking the user a question
|
||||
``help`` A symbol denoting a help message
|
||||
``file-manager`` A symbol denoting a generic file manager application
|
||||
``system-monitor`` A symbol denoting a generic system monitoring/information application
|
||||
``text-editor`` A symbol denoting a generic text editor application
|
||||
======================== ==============================================
|
||||
|
||||
If an icon name is an application name it should be an application identifier,
|
||||
such as the filename of the application's :file:`.desktop` file on Linux or its
|
||||
bundle identifier on macOS. For example if the cross-platform application
|
||||
FooBar has a desktop file named: :file:`foo-bar.desktop` and a bundle
|
||||
identifier of ``net.foo-bar-website.foobar`` then it should use the icon names
|
||||
``net.foo-bar-website.foobar`` *and* ``foo-bar`` so that terminals running on
|
||||
both platforms can find the application icon.
|
||||
|
||||
If no icon is specified, but the ``f`` key (application name) is specified, the
|
||||
terminal emulator should use the value of the ``f`` key to try to find a
|
||||
suitable icon.
|
||||
|
||||
Adding icons by transmitting icon data
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This can be done by using the ``p=icon`` key. Then, the payload is the icon
|
||||
image in any of the ``PNG``, ``JPEG`` or ``GIF`` image formats. It is recommended
|
||||
to use an image size of ``256x256`` for icons. Since icons are binary data,
|
||||
they must be transmitted encoded, with ``e=1``.
|
||||
|
||||
When both an icon name and an image are specified, the terminal emulator must
|
||||
first try to find a locally available icon matching the name and only if one
|
||||
is not found, fallback to the provided image. This is so that users are
|
||||
presented with icons from their current icon theme, where possible.
|
||||
|
||||
Transmitted icon data can be cached using the ``g`` key. The value of the ``g``
|
||||
key must be a random globally unique UUID like :ref:`identifier`. Then, the
|
||||
terminal emulator will cache the transmitted data using that key. The cache
|
||||
should exist for as long as the terminal emulator remains running. Thus, in
|
||||
future notifications, the application can simply send the ``g`` key to display
|
||||
a previously cached icon image with needing to re-transmit the actual data with
|
||||
``p=icon``. The ``g`` key refers only to the icon data, multiple different
|
||||
notifications with different icon or application names can use the same ``g``
|
||||
key to refer to the same icon. Terminal multiplexers must cache icon data
|
||||
themselves and refresh it in the underlying terminal implementation when
|
||||
detaching and then re-attaching. This means that applications once started
|
||||
need to transmit icon data only once until they are quit.
|
||||
*should* either ignore the entire escape code or perform a best guess effort
|
||||
to display it based on what it does understand.
|
||||
|
||||
.. note::
|
||||
To avoid DoS attacks terminal implementations can impose a reasonable max size
|
||||
on the icon cache and evict icons in order of last used. Thus theoretically,
|
||||
a previously cached icon may become unavailable, but given that icons are
|
||||
small images, practically this is not an issue in all but the most resource
|
||||
constrained environments, and the failure mode is simply that the icon is not
|
||||
displayed.
|
||||
It is possible to extend this escape code to allow specifying an icon for
|
||||
the notification, however, given that some platforms, such as legacy versions
|
||||
of macOS, don't allow displaying custom images on a notification, it was
|
||||
decided to leave it out of the spec for the time being.
|
||||
|
||||
.. note::
|
||||
How the icon is displayed depends on the underlying OS notifications
|
||||
implementation. For example, on Linux, typically a single icon is displayed.
|
||||
On macOS, both the terminal emulator's icon and the specified custom icon
|
||||
are displayed.
|
||||
Similarly, features such as scheduled notifications could be added in future
|
||||
revisions.
|
||||
|
||||
|
||||
Adding buttons to the notification
|
||||
---------------------------------------
|
||||
|
||||
Buttons can be added to the notification using the *buttons* payload, with ``p=buttons``.
|
||||
Buttons are a list of UTF-8 text separated by the Unicode Line Separator
|
||||
character (U+2028) which is the UTF-8 bytes ``0xe2 0x80 0xa8``. They can be
|
||||
sent either as :ref:`safe_utf8` or :ref:`base64`. When the user clicks on one
|
||||
of the buttons, and reporting is enabled with ``a=report``, the terminal will
|
||||
send an escape code of the form::
|
||||
|
||||
<OSC> 99 ; i=identifier ; button_number <terminator>
|
||||
|
||||
Here, `button_number` is a number from 1 onwards, where 1 corresponds
|
||||
to the first button, two to the second and so on. If the user activates the
|
||||
notification as a whole, and not a specific button, the response, as described
|
||||
above is::
|
||||
|
||||
<OSC> 99 ; i=identifier ; <terminator>
|
||||
|
||||
If no identifier was specified when creating the notification, ``i=0`` is used.
|
||||
The terminal *must not* send a response unless report is requested with
|
||||
``a=report``.
|
||||
|
||||
.. note::
|
||||
|
||||
The appearance of the buttons depends on the underlying OS implementation.
|
||||
On most Linux systems, the buttons appear as individual buttons on the
|
||||
notification. On macOS they appear as a drop down menu that is accessible
|
||||
when hovering the notification. Generally, using more than two or three
|
||||
buttons is not a good idea.
|
||||
|
||||
.. _notifications_query:
|
||||
|
||||
Playing a sound with notifications
|
||||
-----------------------------------------
|
||||
|
||||
.. versionadded:: 0.36.0
|
||||
The ability to control the sound played with notifications
|
||||
|
||||
By default, notifications may or may not have a sound associated with them
|
||||
depending on the policies of the OS notifications service. Sometimes it
|
||||
might be useful to ensure a notification is not accompanied by a sound.
|
||||
This can be done by using the ``s`` key which accepts :ref:`base64` encoded
|
||||
UTF-8 text as its value. The set of known sounds names is in the table below,
|
||||
any other names are implementation dependent, for instance, on Linux, terminal emulators will
|
||||
probably support the `standard sound names
|
||||
<https://specifications.freedesktop.org/sound-naming-spec/latest/#names>`__
|
||||
|
||||
.. table:: Standard sound names
|
||||
|
||||
======================== ==============================================
|
||||
Name Description
|
||||
======================== ==============================================
|
||||
``system`` The default system sound for a notification, which may be some kind of beep or just silence
|
||||
``silent`` No sound must accompany the notification
|
||||
``error`` A sound associated with error messages
|
||||
``warn``, ``warning`` A sound associated with warning messages
|
||||
``info`` A sound associated with information messages
|
||||
``question`` A sound associated with questions
|
||||
======================== ==============================================
|
||||
|
||||
Support for sound names can be queried as described below.
|
||||
|
||||
|
||||
Querying for support
|
||||
-------------------------
|
||||
|
||||
.. versionadded:: 0.36.0
|
||||
The ability to query for support
|
||||
|
||||
An application can query the terminal emulator for support of this protocol, by
|
||||
sending the following escape code::
|
||||
|
||||
<OSC> 99 ; i=<some identifier> : p=? ; <terminator>
|
||||
|
||||
A conforming terminal must respond with an escape code of the form::
|
||||
|
||||
<OSC> 99 ; i=<some identifier> : p=? ; key=value : key=value <terminator>
|
||||
|
||||
The identifier is present to support terminal multiplexers, so that they know
|
||||
which window to redirect the query response too.
|
||||
|
||||
Here, the ``key=value`` parts specify details about what the terminal
|
||||
implementation supports. Currently, the following keys are defined:
|
||||
|
||||
======= ================================================================================
|
||||
Key Value
|
||||
======= ================================================================================
|
||||
``a`` Comma separated list of actions from the ``a`` key that the terminal
|
||||
implements. If no actions are supported, the ``a`` key must be absent from the
|
||||
query response.
|
||||
|
||||
``c`` ``c=1`` if the terminal supports close events, otherwise the ``c``
|
||||
must be omitted.
|
||||
|
||||
``o`` Comma separated list of occassions from the ``o`` key that the
|
||||
terminal implements. If no occasions are supported, the value
|
||||
``o=always`` must be sent in the query response.
|
||||
|
||||
``p`` Comma 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 atleast ``system`` and ``silent``.
|
||||
|
||||
``u`` Comma separated list of urgency values that the terminal implements.
|
||||
If urgency is not supported, the ``u`` key must be absent from the
|
||||
query response.
|
||||
|
||||
``w`` ``w=1`` if the terminal supports auto expiring of notifications.
|
||||
======= ================================================================================
|
||||
|
||||
In the future, if this protocol expands, more keys might be added. Clients must
|
||||
ignore keys they do not understand in the query response.
|
||||
|
||||
To check if a terminal emulator supports this notifications protocol the best way is to
|
||||
send the above *query action* followed by a request for the `primary device
|
||||
attributes <https://vt100.net/docs/vt510-rm/DA1.html>`_. If you get back an
|
||||
answer for the device attributes without getting back an answer for the *query
|
||||
action* the terminal emulator does not support this notifications protocol.
|
||||
|
||||
.. _keys_in_notificatons_protocol:
|
||||
|
||||
Specification of all keys used in the protocol
|
||||
--------------------------------------------------
|
||||
|
||||
======= ==================== ========== =================
|
||||
Key Value Default Description
|
||||
======= ==================== ========== =================
|
||||
@@ -437,30 +103,16 @@ Key Value Default Description
|
||||
optional leading
|
||||
``-``
|
||||
|
||||
``c`` ``0`` or ``1`` ``0`` When non-zero an escape code is sent to the application when the notification is closed.
|
||||
|
||||
``d`` ``0`` or ``1`` ``1`` Indicates if the notification is
|
||||
complete or not. A non-zero value
|
||||
means it is complete.
|
||||
complete or not.
|
||||
|
||||
``e`` ``0`` or ``1`` ``0`` If set to ``1`` means the payload is :ref:`base64` encoded UTF-8,
|
||||
``e`` ``0`` or ``1`` ``0`` If set to ``1`` means the payload is Base64 encoded UTF-8,
|
||||
otherwise it is plain UTF-8 text with no C0 control codes in it
|
||||
|
||||
``f`` :ref:`base64` ``unset`` The name of the application sending the notification. Can be used to filter out notifications.
|
||||
encoded UTF-8
|
||||
application name
|
||||
``i`` ``[a-zA-Z0-9-_+.]`` ``0`` Identifier for the notification
|
||||
|
||||
``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 unqiue,
|
||||
like an UUID, so that terminal multiplexers can
|
||||
direct responses to the correct window. Note that for backwards
|
||||
compatibility reasons i=0 is special and should not be used.
|
||||
|
||||
``n`` :ref:`base64` ``unset`` Icon name. Can be specified multiple times.
|
||||
encoded UTF-8
|
||||
application name
|
||||
``p`` One of ``title`` or ``title`` Whether the payload is the notification title or body. If a
|
||||
``body``. notification has no title, the body will be used as title.
|
||||
|
||||
``o`` One of ``always``, ``always`` When to honor the notification request. ``unfocused`` means when the window
|
||||
``unfocused`` or the notification is sent on does not have keyboard focus. ``invisible``
|
||||
@@ -468,27 +120,8 @@ Key Value Default Description
|
||||
and not visible to the user, for example, because it is in an inactive tab or
|
||||
its OS window is not currently active.
|
||||
``always`` is the default and always honors the request.
|
||||
|
||||
``p`` One of ``title``, ``title`` Type of the payload. If a notification has no title, the body will be used as title.
|
||||
``body``, A notification with not title and no body is ignored. Terminal
|
||||
``close``, emulators should ignore payloads of unknown type to allow for future
|
||||
``icon``, expansion of this protocol.
|
||||
``?``, ``alive``,
|
||||
``buttons``
|
||||
|
||||
``s`` :ref:`base64` ``system`` The sound name to play with the notification. ``silent`` means no sound.
|
||||
encoded sound ``system`` means to play the default sound, if any, of the platform notification service.
|
||||
name Other names are implementation dependent.
|
||||
|
||||
``t`` :ref:`base64` ``unset`` The type of the notification. Used to filter out notifications. Can be specified multiple times.
|
||||
encoded UTF-8
|
||||
notification type
|
||||
|
||||
``u`` ``0, 1 or 2`` ``unset`` The *urgency* of the notification. ``0`` is low, ``1`` is normal and ``2`` is critical.
|
||||
If not specified normal is used.
|
||||
|
||||
|
||||
``w`` ``>=-1`` ``-1`` The number of milliseconds to auto-close the notification after.
|
||||
======= ==================== ========== =================
|
||||
|
||||
|
||||
@@ -503,49 +136,3 @@ Key Value Default Description
|
||||
|kitty| also supports the `legacy OSC 9 protocol developed by iTerm2
|
||||
<https://iterm2.com/documentation-escape-codes.html>`__ for desktop
|
||||
notifications.
|
||||
|
||||
|
||||
.. _base64:
|
||||
|
||||
Base64
|
||||
---------------
|
||||
|
||||
The base64 encoding used in the this specification is the one defined in
|
||||
:rfc:`4648`. When a base64 payload is chunked, either the chunking should be
|
||||
done before encoding or after. When the chunking is done before encoding, no
|
||||
more than 2048 bytes of data should be encoded per chunk and the encoded data
|
||||
**must** include the base64 padding bytes, if any. When the chunking is done
|
||||
after encoding, each encoded chunk must be no more than 4096 bytes in size.
|
||||
There may or may not be padding bytes at the end of the last chunk, terminals
|
||||
must handle either case.
|
||||
|
||||
|
||||
.. _safe_utf8:
|
||||
|
||||
Escape code safe UTF-8
|
||||
--------------------------
|
||||
|
||||
This must be valid UTF-8 as per the spec in :rfc:`3629`. In addition, in order
|
||||
to make it safe for transmission embedded inside an escape code, it must
|
||||
contain none of the C0 and C1 control characters, that is, the unicode
|
||||
characters: U+0000 (NUL) - U+1F (Unit separator), U+7F (DEL) and U+80 (PAD) - U+9F
|
||||
(APC). Note that in particular, this means that no newlines, carriage returns,
|
||||
tabs, etc. are allowed.
|
||||
|
||||
|
||||
.. _identifier:
|
||||
|
||||
Identifier
|
||||
----------------
|
||||
|
||||
Any string consisting solely of characters from the set ``[a-zA-Z0-9_-+.]``,
|
||||
that is, the letters ``a-z``, ``A-Z``, the underscore, the hyphen, the plus
|
||||
sign and the period. Applications should make these globally unique, like a
|
||||
UUID for maximum robustness.
|
||||
|
||||
|
||||
.. important::
|
||||
Terminals **must** sanitize ids received from client programs before sending
|
||||
them back in responses, to mitigate input injection based attacks. That is, they must
|
||||
either reject ids containing characters not from the above set, or remove
|
||||
bad characters when reading ids sent to them.
|
||||
|
||||
36
docs/faq.rst
36
docs/faq.rst
@@ -257,30 +257,27 @@ fonts to be freely resizable, so it does not support bitmapped fonts.
|
||||
.. note::
|
||||
If you are trying to use a font patched with `Nerd Fonts
|
||||
<https://nerdfonts.com/>`__ symbols, don't do that as patching destroys
|
||||
fonts. There is no need, kitty has a builtin NERD font and will use it for
|
||||
symbols not found in any other font on your system.
|
||||
If you have patched fonts on your system they might be used instead for NERD
|
||||
symbols, so to force kitty to use the pure NERD font for NERD symbols,
|
||||
add the following line to :file:`kitty.conf`::
|
||||
fonts. There is no need, simply install the standalone ``Symbols Nerd Font Mono``
|
||||
(the file :file:`NerdFontsSymbolsOnly.zip` from the `Nerd Fonts releases page
|
||||
<https://github.com/ryanoasis/nerd-fonts/releases>`__). kitty should pick up
|
||||
symbols from it automatically, and you can tell it to do so explicitly in
|
||||
case it doesn't with the :opt:`symbol_map` directive::
|
||||
|
||||
# Nerd Fonts v3.2.0
|
||||
# Nerd Fonts v3.1.0
|
||||
|
||||
symbol_map U+e000-U+e00a,U+ea60-U+ebeb,U+e0a0-U+e0c8,U+e0ca,U+e0cc-U+e0d7,U+e200-U+e2a9,U+e300-U+e3e3,U+e5fa-U+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
|
||||
symbol_map U+e000-U+e00a,U+ea60-U+ebeb,U+e0a0-U+e0c8,U+e0ca,U+e0cc-U+e0d4,U+e200-U+e2a9,U+e300-U+e3e3,U+e5fa-U+e6b1,U+e700-U+e7c5,U+f000-U+f2e0,U+f300-U+f372,U+f400-U+f532,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
|
||||
not included.
|
||||
|
||||
If your font is not listed in ``kitten choose-fonts`` it means that it is not
|
||||
If your font is not listed in ``kitty +list-fonts`` it means that it is not
|
||||
monospace or is a bitmapped font. On Linux you can list all monospace fonts
|
||||
with::
|
||||
|
||||
fc-list : family spacing outline scalable | grep -e spacing=100 -e spacing=90 | grep -e outline=True | grep -e scalable=True
|
||||
|
||||
On macOS, you can open *Font Book* and look in the :guilabel:`Fixed width`
|
||||
collection to see all monospaced fonts on your system.
|
||||
|
||||
Note that **on Linux**, the spacing property is calculated by fontconfig based on actual glyph
|
||||
Note that the spacing property is calculated by fontconfig based on actual glyph
|
||||
widths in the font. If for some reason fontconfig concludes your favorite
|
||||
monospace font does not have ``spacing=100`` you can override it by using the
|
||||
following :file:`~/.config/fontconfig/fonts.conf`::
|
||||
@@ -303,7 +300,7 @@ command to rebuild your fontconfig cache::
|
||||
|
||||
fc-cache -r
|
||||
|
||||
Then, the font will be available in ``kitten choose-fonts``.
|
||||
Then, the font will be available in ``kitty +list-fonts``.
|
||||
|
||||
|
||||
How can I assign a single global shortcut to bring up the kitty terminal?
|
||||
@@ -318,9 +315,8 @@ see :iss:`here <45>`.
|
||||
I do not like the kitty icon!
|
||||
-------------------------------
|
||||
|
||||
The kitty icon was created as tribute to my cat of nine years who passed away,
|
||||
as such it is not going to change. However, if you do not like it, there are
|
||||
many alternate icons available, click on an icon to visit its homepage:
|
||||
There are many alternate icons available, click on an icon to visit its
|
||||
homepage:
|
||||
|
||||
.. image:: https://github.com/k0nserv/kitty-icon/raw/main/kitty.iconset/icon_256x256.png
|
||||
:target: https://github.com/k0nserv/kitty-icon
|
||||
@@ -346,10 +342,6 @@ many alternate icons available, click on an icon to visit its homepage:
|
||||
:target: https://github.com/samholmes/whiskers
|
||||
:width: 256
|
||||
|
||||
.. image:: https://github.com/user-attachments/assets/a37d7830-4a8c-45a8-988a-3e98a41ea541
|
||||
:target: https://github.com/diegobit/kitty-icon
|
||||
:width: 256
|
||||
|
||||
.. image:: https://github.com/eccentric-j/eccentric-icons/raw/main/icons/kitty-terminal/2d/kitty-preview.png
|
||||
:target: https://github.com/eccentric-j/eccentric-icons
|
||||
:width: 256
|
||||
@@ -403,11 +395,9 @@ This is accomplished by using ``map`` with :ac:`send_key` in :file:`kitty.conf`.
|
||||
For example::
|
||||
|
||||
map alt+s send_key ctrl+s
|
||||
map ctrl+alt+2 combine : send_key ctrl+c : send_key h : send_key a
|
||||
|
||||
This causes the program running in kitty to receive the :kbd:`ctrl+s` key when
|
||||
you press the :kbd:`alt+s` key and several keystrokes when you press
|
||||
:kbd:`ctrl+alt+2`. To see this in action, run::
|
||||
you press the :kbd:`alt+s` key. To see this in action, run::
|
||||
|
||||
kitten show-key -m kitty
|
||||
|
||||
|
||||
@@ -245,7 +245,7 @@ File paths
|
||||
path must be no longer than 255 UTF-8 bytes. Total path length must be no
|
||||
more than 4096 bytes. Paths from Windows systems must use the forward slash
|
||||
as the separator, the first path component must be the drive letter with a
|
||||
colon. For example: :file:`C:\\some\\file.txt` is represented as
|
||||
colon. For example: :file:`C:\some\file.txt` is represented as
|
||||
:file:`/C:/some/file.txt`. For maximum portability, the following
|
||||
characters *should* be omitted from paths (however implementations are free
|
||||
to try to support them returning errors for non-representable paths)::
|
||||
|
||||
@@ -50,18 +50,6 @@ Glossary
|
||||
inside kitty windows and provide it with lots of powerful and flexible
|
||||
features such as viewing images, connecting conveniently to remote
|
||||
computers, transferring files, inputting unicode characters, etc.
|
||||
They can also be written by users in Python and used to customize and
|
||||
extend kitty functionality, see :doc:`kittens_intro` for details.
|
||||
|
||||
easing function
|
||||
A function that controls how an animation progresses over time. kitty
|
||||
support the `CSS syntax for easing functions
|
||||
<https://developer.mozilla.org/en-US/docs/Web/CSS/easing-function>`__.
|
||||
Commonly used easing functions are :code:`linear` for a constant rate
|
||||
animation and :code:`ease-in-out` for an animation that starts slow,
|
||||
becomes fast in the middle and ends slowly. These are used to control
|
||||
various animations in kitty, such as :opt:`cursor_blink_interval` and
|
||||
:opt:`visual_bell_duration`.
|
||||
|
||||
.. _env_vars:
|
||||
|
||||
@@ -98,11 +86,6 @@ Variables that influence kitty behavior
|
||||
|
||||
Same as :envvar:`VISUAL`. Used if :envvar:`VISUAL` is not set.
|
||||
|
||||
.. envvar:: SHELL
|
||||
|
||||
Specifies the default shell kitty will run when :opt:`shell` is set to
|
||||
:code:`.`.
|
||||
|
||||
.. envvar:: GLFW_IM_MODULE
|
||||
|
||||
Set this to ``ibus`` to enable support for IME under X11.
|
||||
|
||||
@@ -25,41 +25,38 @@ alpha-blending and text over graphics.
|
||||
:alt: Demo of graphics rendering in kitty
|
||||
:align: center
|
||||
|
||||
Some applications that use the kitty graphics protocol:
|
||||
Some programs and libraries that use the kitty graphics protocol:
|
||||
|
||||
* `awrit <https://github.com/chase/awrit>`_ - Chromium-based web browser rendered in Kitty with mouse and keyboard support
|
||||
* `broot <https://dystroy.org/broot/>`_ - a terminal file explorer and manager, with preview of images, SVG, PDF, etc.
|
||||
* `chafa <https://github.com/hpjansson/chafa>`_ - a terminal image viewer
|
||||
* :doc:`kitty-diff <kittens/diff>` - a side-by-side terminal diff program with support for images
|
||||
* `fzf <https://github.com/junegunn/fzf/commit/d8188fce7b7bea982e7f9050c35e488e49fb8fd0>`_ - A command line fuzzy finder
|
||||
* `mpv <https://github.com/mpv-player/mpv/commit/874e28f4a41a916bb567a882063dd2589e9234e1>`_ - A video player that can play videos in the terminal
|
||||
* `neofetch <https://github.com/dylanaraps/neofetch>`_ - A command line system information tool
|
||||
* `pixcat <https://github.com/mirukana/pixcat>`_ - a third party CLI and python library that wraps the graphics protocol
|
||||
* `ranger <https://github.com/ranger/ranger>`_ - a terminal file manager, with image previews
|
||||
* `termpdf.py <https://github.com/dsanson/termpdf.py>`_ - a terminal PDF/DJVU/CBR viewer
|
||||
* `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
|
||||
* `viu <https://github.com/atanunq/viu>`_ - a terminal image viewer
|
||||
* `ranger <https://github.com/ranger/ranger>`_ - a terminal file manager, with image previews
|
||||
* `Yazi <https://github.com/sxyazi/yazi>`_ - Blazing fast terminal file manager written in Rust, based on async I/O
|
||||
|
||||
Libraries:
|
||||
|
||||
* :doc:`kitty-diff <kittens/diff>` - a side-by-side terminal diff program with support for images
|
||||
* `tpix <https://github.com/jesvedberg/tpix>`_ - a statically compiled binary that can be used to display images and easily installed on remote servers without root access
|
||||
* `mpv <https://github.com/mpv-player/mpv/commit/874e28f4a41a916bb567a882063dd2589e9234e1>`_ - A video player that can play videos in the terminal
|
||||
* `pixcat <https://github.com/mirukana/pixcat>`_ - a third party CLI and python library that wraps the graphics protocol
|
||||
* `neofetch <https://github.com/dylanaraps/neofetch>`_ - A command line system
|
||||
information tool
|
||||
* `viu <https://github.com/atanunq/viu>`_ - a terminal image viewer
|
||||
* `ctx.graphics <https://ctx.graphics/>`_ - Library for drawing graphics
|
||||
* `timg <https://github.com/hzeller/timg>`_ - a terminal image and video viewer
|
||||
* `notcurses <https://github.com/dankamongmen/notcurses>`_ - C library for terminal graphics with bindings for C++, Rust and Python
|
||||
* `rasterm <https://github.com/BourgeoisBear/rasterm>`_ - Go library to display images in the terminal
|
||||
* `chafa <https://github.com/hpjansson/chafa>`_ - a terminal image viewer
|
||||
* `hologram.nvim <https://github.com/edluffy/hologram.nvim>`_ - view images inside nvim
|
||||
* `image.nvim <https://github.com/3rd/image.nvim>`_ - Bringing images to neovim
|
||||
* `image_preview.nvim <https://github.com/adelarsq/image_preview.nvim/>`_ - Image preview for neovim
|
||||
* `kui.nvim <https://github.com/romgrk/kui.nvim>`_ - Build sophisticated UIs inside neovim using the kitty graphics protocol
|
||||
* `term-image <https://github.com/AnonymouX47/term-image>`_ - A Python library, CLI and TUI to display and browse images in the terminal
|
||||
* `glkitty <https://github.com/michaeljclark/glkitty>`_ - C library to draw OpenGL shaders in the terminal with a glgears demo
|
||||
* `twitch-tui <https://github.com/Xithrius/twitch-tui>`_ - Twitch chat in the terminal
|
||||
* `awrit <https://github.com/chase/awrit>`_ - Chromium-based web browser rendered in Kitty with mouse and keyboard support
|
||||
* `fzf <https://github.com/junegunn/fzf/commit/d8188fce7b7bea982e7f9050c35e488e49fb8fd0>`_ - A command line fuzzy finder
|
||||
|
||||
Other terminals that have implemented the graphics protocol:
|
||||
|
||||
* `WezTerm <https://github.com/wez/wezterm/issues/986>`_
|
||||
* `Konsole <https://invent.kde.org/utilities/konsole/-/merge_requests/594>`_
|
||||
* `wayst <https://github.com/91861/wayst>`_
|
||||
* `WezTerm <https://github.com/wez/wezterm/issues/986>`_
|
||||
|
||||
|
||||
Getting the window size
|
||||
@@ -233,7 +230,7 @@ This is a so-called *Application Programming Command (APC)*. Most terminal
|
||||
emulators ignore APC codes, making it safe to use.
|
||||
|
||||
The control data is a comma-separated list of ``key=value`` pairs. The payload
|
||||
is arbitrary binary data, :rfc:`base64 <4648>` encoded to prevent interoperation problems
|
||||
is arbitrary binary data, base64-encoded to prevent interoperation problems
|
||||
with legacy terminals that get confused by control codes within an APC code.
|
||||
The meaning of the payload is interpreted based on the control data.
|
||||
|
||||
@@ -294,8 +291,7 @@ compression is supported, which is specified using ``o=z``. For example::
|
||||
<ESC>_Gf=24,s=10,v=20,o=z;<payload><ESC>\
|
||||
|
||||
This is the same as the example from the RGB data section, except that the
|
||||
payload is now compressed using deflate (this occurs prior to
|
||||
:rfc:`base64 <4648>` encoding).
|
||||
payload is now compressed using deflate (this occurs prior to base64-encoding).
|
||||
The terminal emulator will decompress it before rendering. You can specify
|
||||
compression for any format. The terminal emulator will decompress before
|
||||
interpreting the pixel data.
|
||||
@@ -367,7 +363,7 @@ Remote clients, those that are unable to use the filesystem/shared memory to
|
||||
transmit data, must send the pixel data directly using escape codes. Since
|
||||
escape codes are of limited maximum length, the data will need to be chunked up
|
||||
for transfer. This is done using the ``m`` key. The pixel data must first be
|
||||
:rfc:`base64 <4648>` encoded then chunked up into chunks no larger than ``4096`` bytes. All
|
||||
base64 encoded then chunked up into chunks no larger than ``4096`` bytes. All
|
||||
chunks, except the last, must have a size that is a multiple of 4. The client
|
||||
then sends the graphics escape code as usual, with the addition of an ``m`` key
|
||||
that must have the value ``1`` for all but the last chunk, where it must be
|
||||
@@ -754,9 +750,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
|
||||
@@ -960,7 +953,7 @@ by the ``C`` key with the default being to alpha blend the source rectangle
|
||||
onto the destination rectangle. With ``C=1`` it will be a simple replacement
|
||||
of pixels. For example::
|
||||
|
||||
<ESC>_Ga=c,i=1,r=7,c=9,w=23,h=27,X=4,Y=8,x=1,y=3<ESC>\
|
||||
<ESC>_Gi=1,r=7,c=9,w=23,h=27,X=4,Y=8,x=1,y=3<ESC>\
|
||||
|
||||
Will compose a ``23x27`` rectangle located at ``(4, 8)`` in the ``7th frame``
|
||||
onto the rectangle located at ``(1, 3)`` in the ``9th frame``. These will be
|
||||
|
||||
@@ -19,14 +19,14 @@ kitty
|
||||
|
||||
.. tab:: Fast
|
||||
|
||||
* Uses GPU and SIMD vector CPU instructions for :doc:`best in class <performance>`
|
||||
* Offloads rendering to the GPU for :doc:`lower system load <performance>`
|
||||
* Uses threaded rendering for :iss:`absolutely minimal latency <2701#issuecomment-636497270>`
|
||||
* Performance tradeoffs can be :ref:`tuned <conf-kitty-performance>`
|
||||
|
||||
.. tab:: Capable
|
||||
|
||||
* Graphics, with :doc:`images and animations <graphics-protocol>`
|
||||
* Ligatures, emoji with :opt:`per glyph font substitution <symbol_map>` and :doc:`variable fonts and font features </kittens/choose-fonts>`
|
||||
* Ligatures and emoji, with :opt:`per glyph font substitution <symbol_map>`
|
||||
* :term:`Hyperlinks<hyperlinks>`, with :doc:`configurable actions <open_actions>`
|
||||
|
||||
.. tab:: Scriptable
|
||||
|
||||
@@ -98,9 +98,6 @@ get_release_url() {
|
||||
get_file_url "v$release_version" "$release_version"
|
||||
}
|
||||
|
||||
get_version_url() {
|
||||
get_file_url "v$1" "$1"
|
||||
}
|
||||
|
||||
get_nightly_url() {
|
||||
get_file_url "nightly" "nightly"
|
||||
@@ -111,7 +108,6 @@ get_download_url() {
|
||||
case "$installer" in
|
||||
"nightly") get_nightly_url ;;
|
||||
"") get_release_url ;;
|
||||
version-*) get_version_url "${installer#*-}";;
|
||||
*) installer_is_file="y" ;;
|
||||
esac
|
||||
}
|
||||
@@ -130,24 +126,20 @@ download_installer() {
|
||||
}
|
||||
}
|
||||
|
||||
ensure_dest() {
|
||||
printf "%s\n" "Installing to $dest"
|
||||
command rm -rf "$dest" || die "Failed to delete $dest"
|
||||
command mkdir -p "$dest" || die "Failed to mkdir -p $dest"
|
||||
command rm -rf "$dest" || die "Failed to delete $dest"
|
||||
}
|
||||
|
||||
linux_install() {
|
||||
command mkdir "$tdir/mp"
|
||||
command tar -C "$tdir/mp" "-xJof" "$installer" || die "Failed to extract kitty tarball"
|
||||
ensure_dest
|
||||
printf "%s\n" "Installing to $dest"
|
||||
command rm -rf "$dest" || die "Failed to delete $dest"
|
||||
command mv "$tdir/mp" "$dest" || die "Failed to move kitty.app to $dest"
|
||||
}
|
||||
|
||||
macos_install() {
|
||||
command mkdir "$tdir/mp"
|
||||
command hdiutil attach "$installer" "-mountpoint" "$tdir/mp" || die "Failed to mount kitty.dmg"
|
||||
ensure_dest
|
||||
printf "%s\n" "Installing to $dest"
|
||||
command rm -rf "$dest"
|
||||
command mkdir -p "$dest" || die "Failed to create the directory: $dest"
|
||||
command ditto -v "$tdir/mp/kitty.app" "$dest"
|
||||
rc="$?"
|
||||
command hdiutil detach "$tdir/mp"
|
||||
|
||||
@@ -26,12 +26,6 @@ A terminal PDF/DJVU/CBR viewer
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
A terminal PDF viewer
|
||||
|
||||
.. _tool_fancy_cat:
|
||||
|
||||
`fancy-cat <https://github.com/freref/fancy-cat>`_
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
A terminal PDF viewer
|
||||
|
||||
.. _tool_mdcat:
|
||||
|
||||
`mdcat <https://github.com/lunaryorn/mdcat>`_
|
||||
@@ -106,20 +100,11 @@ 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>`_
|
||||
@@ -147,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:
|
||||
|
||||
@@ -294,14 +275,6 @@ Keyboard based text selection for the kitty scrollback buffer.
|
||||
Miscellaneous
|
||||
------------------
|
||||
|
||||
.. tool_gattino:
|
||||
|
||||
`gattino <https://github.com/salvozappa/gattino>`__
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Integrate kitty with an LLM to convert plain language prompts into shell
|
||||
commands.
|
||||
|
||||
.. tool_kitty_smart_tab:
|
||||
|
||||
`kitty-smart-tab <https://github.com/yurikhan/kitty-smart-tab>`_
|
||||
|
||||
@@ -38,17 +38,11 @@ In addition to kitty, this protocol is also implemented in:
|
||||
* The `WezTerm terminal <https://wezfurlong.org/wezterm/config/lua/config/enable_kitty_keyboard.html>`__
|
||||
* The `alacritty terminal <https://github.com/alacritty/alacritty/pull/7125>`__
|
||||
* The `rio terminal <https://github.com/raphamorim/rio/commit/cd463ca37677a0fc48daa8795ea46dadc92b1e95>`__
|
||||
* The `iTerm2 terminal <https://gitlab.com/gnachman/iterm2/-/issues/10017>`__
|
||||
|
||||
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 `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/>`__
|
||||
|
||||
Programs implementing this protocol:
|
||||
|
||||
* The `Vim text editor <https://github.com/vim/vim/commit/63a2e360cca2c70ab0a85d14771d3259d4b3aafa>`__
|
||||
* The `Emacs text editor via the kkp package <https://github.com/benjaminor/kkp>`__
|
||||
* The `Neovim text editor <https://github.com/neovim/neovim/pull/18181>`__
|
||||
@@ -58,11 +52,6 @@ Programs implementing this protocol:
|
||||
* 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:
|
||||
|
||||
* The `nushell shell <https://github.com/nushell/nushell/pull/10540>`__
|
||||
* The `fish shell <https://github.com/fish-shell/fish-shell/commit/8bf8b10f685d964101f491b9cc3da04117a308b4>`__
|
||||
|
||||
|
||||
@@ -45,84 +45,7 @@ will be automatically adjusted based on what you select for the regular face.
|
||||
|
||||
You can choose a specific style or font feature by clicking on it. A precise
|
||||
value for any variable axes can be selected using the slider, in the screenshot
|
||||
above, the font supports precise weight adjustment. If you are lucky the font
|
||||
above the font supports precise weight adjustment. If you are lucky the font
|
||||
designer has included descriptive names for font features, which will be
|
||||
displayed, if not, consult the documentation of the font to see what each feature does.
|
||||
|
||||
.. _font_spec_syntax:
|
||||
|
||||
The font specification syntax
|
||||
--------------------------------
|
||||
|
||||
If you don't like the choose fonts kitten or simply want to understand and
|
||||
write font selection options into :file:`kitty.conf` yourself, read on.
|
||||
|
||||
There are four font face selection keys: `font_family`, `bold_font`,
|
||||
`italic_font` and `bold_italic_font`. Each of these supports the syntax
|
||||
described below. Their values can be of three types, either a
|
||||
font family name, the keyword ``auto`` or an extended ``key=value`` syntax
|
||||
for specifying font selection precisely.
|
||||
|
||||
If a font family name is specified kitty will use Operating System APIs to
|
||||
search for a matching font. The keyword ``auto`` means kitty will choose a font
|
||||
completely automatically, typically this is used for automatically selecting
|
||||
bold/italic variants once the :opt:`font_family` is set. The bold and italic
|
||||
variants will then automatically use the same set of features as the main face.
|
||||
|
||||
To specify font face selection more precisely, a ``key=value`` syntax is used.
|
||||
First, let's look at a few examples::
|
||||
|
||||
# Select by family only, actual face selection is automatic
|
||||
font_family family="Fira Code"
|
||||
# Select an exact face by Postscript name
|
||||
font_family postscript_name=FiraCode
|
||||
# Select an exact face by family with features and variable weight
|
||||
font_family family=SourceCodeVF variable_name=SourceCodeUpright features="+zero cv01=2" wght=380
|
||||
|
||||
The following are the known keys, any other keys are names of *variable axes*,
|
||||
that is, they are used to set the variable value for some font characteristic.
|
||||
|
||||
``family``
|
||||
A font family name. A family typically has multiple actual font faces, such
|
||||
as bold and italic variants. One or more of the faces can even be variable,
|
||||
allowing fine tuning of font characteristics.
|
||||
|
||||
``style``
|
||||
A style name to choose a particular font from a given family. Useful only
|
||||
with the ``family`` key, when no more precise methods for face selection
|
||||
are specified. Can also be used to specify a named variable style for
|
||||
variable fonts.
|
||||
|
||||
``postscript_name``
|
||||
The actual postscript name for a font face. This allows selecting a
|
||||
particular variant within a font family. But note that postscript names
|
||||
are usually insufficient for selecting variable fonts.
|
||||
|
||||
``full_name``
|
||||
This can be used to select a particular font face in a family. However, it
|
||||
is less precise than ``postscript_name`` and should not generally be used.
|
||||
|
||||
``variable_name``
|
||||
Some families with variable fonts actually contain multiple font files. For
|
||||
example, a family could have variable weights with one font file containing
|
||||
upright variable weight faces and another containing italic variable weight
|
||||
faces. Well designed fonts use a *variable name* to distinguish between
|
||||
such files. Should be used in conjunction with ``family`` to select a
|
||||
particular variable font file.
|
||||
|
||||
``features``
|
||||
A space separated list of OpenType font features to enable/disable or
|
||||
select a value of, for this font. Consult the documentation for the font
|
||||
family to see what features it supports and their effects. The exact syntax
|
||||
for specifying features is `documented by HarfBuzz
|
||||
<https://harfbuzz.github.io/harfbuzz-hb-common.html#hb-feature-from-string>`__
|
||||
|
||||
``system``
|
||||
This can be used to pass an arbitrary string, usuall a family or full name
|
||||
to the OS font selection APIs. Should not be used in conjunction with any
|
||||
other keys. Is the same as specifying just the font name without any keys.
|
||||
|
||||
|
||||
In addition to these keys, any four letter key is treated as the name of a
|
||||
variable characteristic of the font. It's value is used to set the value for
|
||||
the name.
|
||||
|
||||
@@ -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:
|
||||
@@ -72,9 +73,6 @@ would pass to ``kitten @``. For example:
|
||||
shown above or ``--self``.
|
||||
|
||||
|
||||
Run, ``kitten @ --help`` in a kitty terminal, to see all the remote control
|
||||
commands available to you.
|
||||
|
||||
Passing arguments to kittens
|
||||
------------------------------
|
||||
|
||||
@@ -100,18 +98,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
|
||||
|
||||
|
||||
@@ -169,14 +168,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 +228,6 @@ The function will only send the event if the program is receiving events of
|
||||
that type, and will return ``True`` if it sent the event, and ``False`` if not.
|
||||
|
||||
|
||||
.. _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
|
||||
--------------------
|
||||
|
||||
@@ -398,14 +348,6 @@ See `the code <https://github.com/kovidgoyal/kitty/tree/master/kittens/diff>`__
|
||||
for the builtin :doc:`diff kitten </kittens/diff>` for examples of creating more
|
||||
options and keyboard shortcuts.
|
||||
|
||||
|
||||
Developing builtin kittens for inclusion with kitty
|
||||
----------------------------------------------------------
|
||||
|
||||
There is documentation for :doc:`developing-builtin-kittens` which are written in the Go
|
||||
language.
|
||||
|
||||
|
||||
.. _external_kittens:
|
||||
|
||||
Kittens created by kitty users
|
||||
@@ -418,10 +360,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
|
||||
|
||||
|
||||
`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.
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
Developing builtin kittens
|
||||
=============================
|
||||
|
||||
Builtin kittens in kitty are written in the Go language, with small Python
|
||||
wrapper scripts to define command line options and handle UI integration.
|
||||
|
||||
Getting started
|
||||
-----------------------
|
||||
|
||||
To get started with creating a builtin kitten, one that will become part of kitty
|
||||
and be available as ``kitten my-kitten``, create a directory named
|
||||
:file:`my_kitten` in the :file:`kittens` directory. Then, in this directory
|
||||
add three, files: :file:`__init__.py` (an empty file), :file:`__main__.py` and
|
||||
:file:`main.go`.
|
||||
|
||||
Template for `main.py`
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The file :file:`main.py` contains the command line option definitions for your kitten. Change the actual options and help text below as needed.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import sys
|
||||
|
||||
# See the file kitty/cli.py in the kitty sourcecode for more examples of
|
||||
# the syntax for defining options
|
||||
OPTIONS = r'''
|
||||
--some-string-option -s
|
||||
default=my_default_value
|
||||
Help text for a simple option taking a string value.
|
||||
|
||||
|
||||
--some-boolean-option -b
|
||||
type=bool-set
|
||||
Help text for a boolean option defaulting to false.
|
||||
|
||||
|
||||
--some-inverted-boolean-option
|
||||
type=bool-unset
|
||||
Help text for a boolean option defaulting to true.
|
||||
|
||||
|
||||
--an-integer-option
|
||||
type=int
|
||||
default=13
|
||||
bla bla
|
||||
|
||||
|
||||
--an-enum-option
|
||||
choices=a,b,c,d
|
||||
default=a
|
||||
This option can only take the values a, b, c, or d
|
||||
'''.format
|
||||
|
||||
help_text = '''\
|
||||
The introductory help text for your kitten.
|
||||
|
||||
Can contain multiple paragraphs with :bold:`bold`
|
||||
:green:`colored`, :code:`code`, :link:`links <http://url>` etc.
|
||||
formatting.
|
||||
|
||||
Option help strings can also use this formatting.
|
||||
'''
|
||||
|
||||
# The usage string for your kitten
|
||||
usage = 'TITLE [BODY ...]'
|
||||
short_description = 'some short description of your kitten it will show up when running kitten without arguments to list all kittens`
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit('This should be run as kitten my-kitten')
|
||||
elif __name__ == '__doc__':
|
||||
cd = sys.cli_docs # type: ignore
|
||||
cd['usage'] = usage
|
||||
cd['options'] = OPTIONS
|
||||
cd['help_text'] = help_text
|
||||
cd['short_desc'] = short_description
|
||||
|
||||
|
||||
Template for `main.go`
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
package my_kitten
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"kitty/tools/cli"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
func main(_ *cli.Command, opts *Options, args []string) (rc int, err error) {
|
||||
// Here rc is the exit code for the kitten which should be 1 or higher if err is not nil
|
||||
fmt.Println("Hello world!")
|
||||
fmt.Println(args)
|
||||
fmt.Println(fmt.Sprintf("%#v", opts))
|
||||
return
|
||||
}
|
||||
|
||||
func EntryPoint(parent *cli.Command) {
|
||||
create_cmd(parent, main)
|
||||
}
|
||||
|
||||
Edit :file:`tools/cmd/tool/main.go`
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Add the entry point of the kitten into :file:`tools/cmd/tool/main.go`.
|
||||
|
||||
First, import the kitten into this file. To do this, add :code:`"kitty/kittens/my_kitten"` into the :code:`import ( ... )` section at the top.
|
||||
Then, add ``my_kitten.EntryPoint(root)`` into ``func KittyToolEntryPoints(root *cli.Command)`` and you are done. After running make you should
|
||||
be able to test your kitten by running::
|
||||
|
||||
kitten my-kitten
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
notify
|
||||
==================================================
|
||||
|
||||
.. only:: man
|
||||
|
||||
Overview
|
||||
--------------
|
||||
|
||||
Show pop-up system notifications.
|
||||
|
||||
.. highlight:: sh
|
||||
|
||||
.. versionadded:: 0.36.0
|
||||
The notify kitten
|
||||
|
||||
The ``notify`` kitten can be used to show pop-up system notifications
|
||||
from the shell. It even works over SSH. Using it is as simple as::
|
||||
|
||||
kitten notify "Good morning" Hello world, it is a nice day!
|
||||
|
||||
To add an icon, use::
|
||||
|
||||
kitten notify --icon-path /path/to/some/image.png "Good morning" Hello world, it is a nice day!
|
||||
kitten notify --icon firefox "Good morning" Hello world, it is a nice day!
|
||||
|
||||
|
||||
To be informed when the notification is activated::
|
||||
|
||||
kitten notify --wait-for-completion "Good morning" Hello world, it is a nice day!
|
||||
|
||||
Then, the kitten will wait till the notification is either closed or activated.
|
||||
If activated, a ``0`` is printed to :file:`STDOUT`. You can press the
|
||||
:kbd:`Esc` or :kbd:`Ctrl+c` keys to abort, closing the notification.
|
||||
|
||||
To add buttons to the notification::
|
||||
|
||||
kitten notify --wait-for-completion --button One --button Two "Good morning" Hello world, it is a nice day!
|
||||
|
||||
.. program:: kitty +kitten notify
|
||||
|
||||
.. tip:: Learn about the underlying :doc:`/desktop-notifications` escape code protocol.
|
||||
|
||||
.. include:: /generated/cli-kitten-notify.rst
|
||||
@@ -116,14 +116,14 @@ this could be achieved using the ssh kitten with :program:`zsh` and
|
||||
hostname myserver-*
|
||||
|
||||
# Setup zsh to read its files from my-conf/zsh
|
||||
env ZDOTDIR=$HOME/my-conf/zsh
|
||||
env ZDOTDIR $HOME/my-conf/zsh
|
||||
copy --dest my-conf/zsh/.zshrc .zshrc
|
||||
copy --dest my-conf/zsh/.zshenv .zshenv
|
||||
# If you use other zsh init files add them in a similar manner
|
||||
|
||||
# Setup vim to read its config from my-conf/vim
|
||||
env VIMINIT=$HOME/my-conf/vim/vimrc
|
||||
env VIMRUNTIME=$HOME/my-conf/vim
|
||||
env VIMINIT $HOME/my-conf/vim/vimrc
|
||||
env VIMRUNTIME $HOME/my-conf/vim
|
||||
copy --dest my-conf/vim .vim
|
||||
copy --dest my-conf/vim/vimrc .vimrc
|
||||
|
||||
|
||||
@@ -42,42 +42,6 @@ existing color settings in :file:`kitty.conf` so they do not interfere.
|
||||
|
||||
Once that's done, the kitten sends kitty a signal to make it reload its config.
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
If you want to have some color settings in your :file:`kitty.conf` that the
|
||||
theme kitten does not override, move them into a separate conf file and
|
||||
``include`` it into kitty.conf. The include should be placed after the
|
||||
inclusion of :file:`current-theme.conf` so that the settings in it override
|
||||
conflicting settings from :file:`current-theme.conf`.
|
||||
|
||||
|
||||
.. _auto_color_scheme:
|
||||
|
||||
Change color themes automatically when the OS switches between light and dark
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
.. versionadded:: 0.38.0
|
||||
|
||||
You can have kitty automatically change its color theme when the OS switches
|
||||
between dark, light and no-preference modes. In order to do this, run the theme
|
||||
kitten as normal and at the final screen select the option to save your chosen
|
||||
theme as either light, dark, or no-preference. Repeat until you have chosen
|
||||
a theme for each of the three modes. Then, once you restart kitty, it will
|
||||
automatically use your chosen themes depending on the OS color scheme.
|
||||
|
||||
This works by creating three files: :file:`dark-theme.auto.conf`,
|
||||
:file:`light-theme.auto.conf` and :file:`no-preference-theme.auto.conf` in the
|
||||
kitty config directory. When these files exist, kitty queries the OS for its color scheme
|
||||
and uses the appropriate file. Note that the colors in these files override all other
|
||||
colors, even those specified using the :option:`kitty --override` command line flag.
|
||||
kitty will also automatically change colors when the OS color scheme changes,
|
||||
for example, during night/day transitions.
|
||||
|
||||
When using these colors, you can still dynamically change colors, but the next
|
||||
time the OS changes its color mode, any dynamics changes will be overridden.
|
||||
|
||||
|
||||
Using your own themes
|
||||
-----------------------
|
||||
|
||||
|
||||
@@ -114,76 +114,49 @@ Watching launched windows
|
||||
|
||||
The :option:`launch --watcher` option allows you to specify Python functions
|
||||
that will be called at specific events, such as when the window is resized or
|
||||
closed. Note that you can also specify watchers that are loaded for all windows,
|
||||
via :opt:`watcher`. To create a watcher, specify the path to a Python module
|
||||
that specifies callback functions for the events you are interested in, for
|
||||
create :file:`~/.config/kitty/mywatcher.py` and use :option:`launch --watcher` = :file:`mywatcher.py`:
|
||||
closed. Simply specify the path to a Python module that specifies callback
|
||||
functions for the events you are interested in, for example:
|
||||
|
||||
.. 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 initializztion/one
|
||||
# time setup. Any exceptions in this function are printed to kitty's
|
||||
# STDERR but otherwise ignored.
|
||||
...
|
||||
|
||||
def on_resize(boss: Boss, window: Window, data: dict[str, Any]) -> None:
|
||||
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.
|
||||
...
|
||||
|
||||
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
|
||||
the watcher scripts, however kitty internals are not documented/stable so for
|
||||
most things you are better off using the kitty :doc:`Remote control API </remote-control>`.
|
||||
Simply call :code:`boss.call_remote_control()`, with the same arguments you
|
||||
would pass to ``kitten @``. For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def on_resize(boss: Boss, window: Window, data: dict[str, Any]) -> None:
|
||||
# send some text to the resized window
|
||||
boss.call_remote_control(window, ('send-text', f'--match=id:{window.id}', 'hello world'))
|
||||
|
||||
Run, ``kitten @ --help`` in a kitty terminal, to see all the remote control
|
||||
commands available to you.
|
||||
that contains event dependent data. Some useful methods and attributes for the
|
||||
``Window`` object are: ``as_text(as_ans=False, add_history=False,
|
||||
add_wrap_markers=False, alternate_screen=False)`` with which you can get the
|
||||
contents of the window and its scrollback buffer. Similarly,
|
||||
``window.child.pid`` is the PID of the processes that was launched
|
||||
in the window and ``window.id`` is the internal kitty ``id`` of the window.
|
||||
|
||||
|
||||
Finding executables
|
||||
|
||||
@@ -184,13 +184,11 @@ 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
|
||||
|
||||
|
||||
@@ -201,8 +201,8 @@ In order to make this work, you need to configure your editor as show below:
|
||||
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
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
Miscellaneous protocol extensions
|
||||
==============================================
|
||||
|
||||
These are a few small protocol extensions kitty implements, primarily for use
|
||||
by its own kitten, they are documented here for completeness.
|
||||
|
||||
|
||||
Simple save/restore of all terminal modes
|
||||
--------------------------------------------
|
||||
|
||||
XTerm has the XTSAVE/XTRESTORE escape codes to save and restore terminal
|
||||
private modes. However, they require specifying an explicit list of modes to
|
||||
save/restore. kitty extends this protocol to specify that when no modes are
|
||||
specified, all side-effect free modes should be saved/restored. By side-effects
|
||||
we mean things that can affect other terminal state such as cursor position or
|
||||
screen contents. Examples of modes that have side effects are: `DECOM
|
||||
<https://vt100.net/docs/vt510-rm/DECOM.html>`__ and `DECCOLM
|
||||
<https://vt100.net/docs/vt510-rm/DECCOLM.html>`__.
|
||||
|
||||
This allows TUI applications to easily save and restore emulator state without
|
||||
needing to maintain lists of modes.
|
||||
|
||||
|
||||
Independent control of bold and faint SGR properties
|
||||
-------------------------------------------------------
|
||||
|
||||
In common terminal usage, bold is set via SGR 1 and faint by SGR 2. However,
|
||||
there is only one number to reset these attributes, SGR 22, which resets both.
|
||||
There is no way to reset one and not the other. kitty uses 221 and 222 to reset
|
||||
bold and faint independently.
|
||||
|
||||
|
||||
kitty specific private escape codes
|
||||
---------------------------------------
|
||||
|
||||
These are a family of escape codes used by kitty for various things including
|
||||
remote control. They are all DCS (Device Control String) escape codes starting
|
||||
with ``\x1b P @ kitty-`` (ignoring spaces present for clarity).
|
||||
@@ -1,61 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# A sample script to process notifications. Save it as
|
||||
# ~/.config/kitty/notifications.py
|
||||
|
||||
import subprocess
|
||||
|
||||
from kitty.notifications import NotificationCommand, Urgency
|
||||
|
||||
|
||||
def log_notification(nc: NotificationCommand) -> None:
|
||||
# Log notifications to /tmp/notifications-log.txt
|
||||
with open('/tmp/notifications-log.txt', 'a') as log:
|
||||
print(f'title: {nc.title}', file=log)
|
||||
print(f'body: {nc.body}', file=log)
|
||||
print(f'app: {nc.application_name}', file=log)
|
||||
print(f'types: {nc.notification_types}', file=log)
|
||||
print('\n', file=log)
|
||||
|
||||
|
||||
def on_notification_activated(nc: NotificationCommand, which: int) -> None:
|
||||
# do something when this notification is activated (clicked on)
|
||||
# remember to assign this to the on_activation field in main()
|
||||
pass
|
||||
|
||||
|
||||
def main(nc: NotificationCommand) -> bool:
|
||||
'''
|
||||
This function should return True to filter out the notification
|
||||
'''
|
||||
log_notification(nc)
|
||||
|
||||
# filter out notifications with 'unwanted' in their titles
|
||||
if 'unwanted' in nc.title.lower():
|
||||
return True
|
||||
|
||||
# force the notification to be silent
|
||||
nc.sound_name = 'silent'
|
||||
|
||||
# filter out notifications from the application badapp
|
||||
if nc.application_name == 'badapp':
|
||||
return True
|
||||
|
||||
# filter out low urgency notifications
|
||||
if nc.urgency is Urgency.Low:
|
||||
return True
|
||||
|
||||
# replace some bad text in the notification body
|
||||
nc.body = nc.body.replace('bad text', 'good text')
|
||||
|
||||
# run a script if this notification is from myapp and has
|
||||
# type foo, passing in the title and body as command line args
|
||||
# to the script.
|
||||
if nc.application_name == 'myapp' and 'foo' in nc.notification_types:
|
||||
subprocess.Popen(['/path/to/my/script', nc.title, nc.body])
|
||||
|
||||
# do some arbitrary actions when this notification is activated
|
||||
nc.on_activation = on_notification_activated
|
||||
|
||||
# dont filter out this notification
|
||||
return False
|
||||
@@ -146,8 +146,6 @@ option in :file:`kitty.conf`. An example, showing all available commands:
|
||||
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
|
||||
@@ -178,30 +176,12 @@ option in :file:`kitty.conf`. An example, showing all available commands:
|
||||
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 form :code:`${NAME}` or :code:`$NAME` are
|
||||
Environment variables of the for :code:`${NAME}` or :code:`$NAME` are
|
||||
expanded in the session file, except in the *arguments* (not options) to the
|
||||
launch command.
|
||||
|
||||
|
||||
@@ -34,4 +34,3 @@ please do so by opening issues in the `GitHub bug tracker
|
||||
color-stack
|
||||
deccara
|
||||
clipboard
|
||||
misc-protocol
|
||||
|
||||
@@ -456,13 +456,6 @@ to control its behavior, separated by semi-colons. They are::
|
||||
k=s - this tells kitty that the secondary (PS2) prompt is starting at the
|
||||
current line.
|
||||
|
||||
click_events=1 - this tells kitty that the shell is capable of handling
|
||||
mouse click events. kitty will thus send a click event to the shell when
|
||||
the user clicks somewhere in the prompt. The shell can then move the cursor
|
||||
to that position or perform some other appropriate action. Without this,
|
||||
kitty will instead generate a number of fake key events to move the cursor
|
||||
to the clicked location, which is not fully robust.
|
||||
|
||||
kitty also optionally supports sending the cmdline going to be executed with ``<OSC>133;C`` as::
|
||||
|
||||
<OSC>133;C;cmdline=cmdline encoded by %q<ST>
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
from typing import List
|
||||
|
||||
|
||||
def main(args: list[str]=sys.argv) -> None:
|
||||
def main(args: List[str]=sys.argv) -> None:
|
||||
os.chdir(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
sys.path.insert(0, os.getcwd())
|
||||
if len(args) == 1:
|
||||
|
||||
@@ -5,18 +5,18 @@ import os
|
||||
import subprocess
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from typing import Any, DefaultDict, Union
|
||||
from typing import Any, DefaultDict, Dict, FrozenSet, List, Tuple, Union
|
||||
|
||||
if __name__ == '__main__' and not __package__:
|
||||
import __main__
|
||||
__main__.__package__ = 'gen'
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
KeymapType = dict[str, tuple[str, Union[frozenset[str], str]]]
|
||||
KeymapType = Dict[str, Tuple[str, Union[FrozenSet[str], str]]]
|
||||
|
||||
|
||||
def resolve_keys(keymap: KeymapType) -> DefaultDict[str, list[str]]:
|
||||
ans: DefaultDict[str, list[str]] = defaultdict(list)
|
||||
def resolve_keys(keymap: KeymapType) -> DefaultDict[str, List[str]]:
|
||||
ans: DefaultDict[str, List[str]] = defaultdict(list)
|
||||
for ch, (attr, atype) in keymap.items():
|
||||
if isinstance(atype, str) and atype in ('int', 'uint'):
|
||||
q = atype
|
||||
@@ -45,7 +45,7 @@ def parse_key(keymap: KeymapType) -> str:
|
||||
return ' \n'.join(lines)
|
||||
|
||||
|
||||
def parse_flag(keymap: KeymapType, type_map: dict[str, Any], command_class: str) -> str:
|
||||
def parse_flag(keymap: KeymapType, type_map: Dict[str, Any], command_class: str) -> str:
|
||||
lines = []
|
||||
for ch in type_map['flag']:
|
||||
attr, allowed_values = keymap[ch]
|
||||
@@ -63,14 +63,14 @@ def parse_flag(keymap: KeymapType, type_map: dict[str, Any], command_class: str)
|
||||
return ' \n'.join(lines)
|
||||
|
||||
|
||||
def parse_number(keymap: KeymapType) -> tuple[str, str]:
|
||||
def parse_number(keymap: KeymapType) -> Tuple[str, str]:
|
||||
int_keys = [f'I({attr})' for attr, atype in keymap.values() if atype == 'int']
|
||||
uint_keys = [f'U({attr})' for attr, atype in keymap.values() if atype == 'uint']
|
||||
return '; '.join(int_keys), '; '.join(uint_keys)
|
||||
|
||||
|
||||
def cmd_for_report(report_name: str, keymap: KeymapType, type_map: dict[str, Any], payload_allowed: bool) -> str:
|
||||
def group(atype: str, conv: str) -> tuple[str, str]:
|
||||
def 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]
|
||||
for ch in type_map[atype]:
|
||||
@@ -293,7 +293,7 @@ def graphics_parser() -> None:
|
||||
write_header(text, 'kitty/parse-graphics-command.h')
|
||||
|
||||
|
||||
def main(args: list[str]=sys.argv) -> None:
|
||||
def main(args: List[str]=sys.argv) -> None:
|
||||
graphics_parser()
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import List
|
||||
|
||||
from kitty.conf.generate import write_output
|
||||
|
||||
@@ -15,7 +16,7 @@ if __name__ == '__main__' and not __package__:
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
|
||||
def patch_color_list(path: str, colors: list[str], name: str, spc: str = ' ') -> None:
|
||||
def patch_color_list(path: str, colors: List[str], name: str, spc: str = ' ') -> None:
|
||||
with open(path, 'r+') as f:
|
||||
raw = f.read()
|
||||
colors = sorted(colors)
|
||||
@@ -39,8 +40,9 @@ def patch_color_list(path: str, colors: list[str], name: str, spc: str = ' ')
|
||||
subprocess.check_call(['gofmt', '-w', path])
|
||||
|
||||
|
||||
def main(args: list[str]=sys.argv) -> None:
|
||||
def main(args: List[str]=sys.argv) -> None:
|
||||
from kitty.options.definition import definition
|
||||
write_output('kitty', definition)
|
||||
nullable_colors = []
|
||||
all_colors = []
|
||||
for opt in definition.iter_all_options():
|
||||
@@ -50,10 +52,9 @@ def main(args: list[str]=sys.argv) -> None:
|
||||
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('kitty/rc/set_colors.py', nullable_colors, 'NULLABLE')
|
||||
patch_color_list('tools/cmd/at/set_colors.go', nullable_colors, 'NULLABLE')
|
||||
patch_color_list('tools/themes/collection.go', all_colors, 'ALL')
|
||||
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__':
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import List
|
||||
|
||||
if __name__ == '__main__' and not __package__:
|
||||
import __main__
|
||||
@@ -57,7 +58,7 @@ grabbing grabbing grabbing,closedhand,dnd-none grabbing
|
||||
'''
|
||||
|
||||
|
||||
def main(args: list[str]=sys.argv) -> None:
|
||||
def main(args: List[str]=sys.argv) -> None:
|
||||
glfw_enum = []
|
||||
css_names = []
|
||||
glfw_xc_map = {}
|
||||
|
||||
@@ -12,15 +12,20 @@ import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
from collections.abc import Iterator, Sequence
|
||||
from contextlib import contextmanager, suppress
|
||||
from functools import lru_cache
|
||||
from itertools import chain
|
||||
from typing import (
|
||||
Any,
|
||||
BinaryIO,
|
||||
Dict,
|
||||
Iterator,
|
||||
List,
|
||||
Optional,
|
||||
Sequence,
|
||||
Set,
|
||||
TextIO,
|
||||
Tuple,
|
||||
Union,
|
||||
)
|
||||
|
||||
@@ -37,7 +42,6 @@ from kitty.cli import (
|
||||
)
|
||||
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
|
||||
@@ -52,7 +56,7 @@ if __name__ == '__main__' and not __package__:
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
|
||||
changed: list[str] = []
|
||||
changed: List[str] = []
|
||||
|
||||
|
||||
def newer(dest: str, *sources: str) -> bool:
|
||||
@@ -70,7 +74,7 @@ def newer(dest: str, *sources: str) -> bool:
|
||||
|
||||
# Utils {{{
|
||||
|
||||
def serialize_go_dict(x: Union[dict[str, int], dict[int, str], dict[int, int], dict[str, str]]) -> str:
|
||||
def serialize_go_dict(x: Union[Dict[str, int], Dict[int, str], Dict[int, int], Dict[str, str]]) -> str:
|
||||
ans = []
|
||||
|
||||
def s(x: Union[int, str]) -> str:
|
||||
@@ -188,7 +192,7 @@ def kitten_cli_docs(kitten: str) -> Any:
|
||||
|
||||
|
||||
@lru_cache
|
||||
def go_options_for_kitten(kitten: str) -> tuple[Sequence[GoOption], Optional[CompletionSpec]]:
|
||||
def go_options_for_kitten(kitten: str) -> Tuple[Sequence[GoOption], Optional[CompletionSpec]]:
|
||||
kcd = kitten_cli_docs(kitten)
|
||||
if kcd:
|
||||
ospec = kcd['options']
|
||||
@@ -293,7 +297,7 @@ def generate_completions_for_kitty() -> None:
|
||||
|
||||
|
||||
# rc command wrappers {{{
|
||||
json_field_types: dict[str, str] = {
|
||||
json_field_types: Dict[str, str] = {
|
||||
'bool': 'bool', 'str': 'escaped_string', 'list.str': '[]escaped_string', 'dict.str': 'map[escaped_string]escaped_string', 'float': 'float64', 'int': 'int',
|
||||
'scroll_amount': 'any', 'spacing': 'any', 'colors': 'any',
|
||||
}
|
||||
@@ -315,16 +319,10 @@ def go_field_type(json_field_type: str) -> str:
|
||||
|
||||
class JSONField:
|
||||
|
||||
def __init__(self, line: str, field_to_option_map: dict[str, str], option_map: dict[str, GoOption]) -> None:
|
||||
def __init__(self, line: str) -> None:
|
||||
field_def = line.split(':', 1)[0]
|
||||
self.required = False
|
||||
self.field, self.field_type = field_def.split('/', 1)
|
||||
self.go_option_name = field_to_option_map.get(self.field, self.field)
|
||||
self.go_option_name = ''.join(x.capitalize() for x in self.go_option_name.split('_'))
|
||||
self.omitempty = True
|
||||
if fo := option_map.get(self.go_option_name):
|
||||
if fo.type in ('int', 'float') and float(fo.default or 0) != 0:
|
||||
self.omitempty = False
|
||||
self.field_type, self.special_parser = self.field_type.partition('=')[::2]
|
||||
if self.field.endswith('+'):
|
||||
self.required = True
|
||||
@@ -332,44 +330,45 @@ class JSONField:
|
||||
self.struct_field_name = self.field[0].upper() + self.field[1:]
|
||||
|
||||
def go_declaration(self) -> str:
|
||||
omitempty = ',omitempty' if self.omitempty else ''
|
||||
return self.struct_field_name + ' ' + go_field_type(self.field_type) + f'`json:"{self.field}{omitempty}"`'
|
||||
return self.struct_field_name + ' ' + go_field_type(self.field_type) + f'`json:"{self.field},omitempty"`'
|
||||
|
||||
|
||||
def go_code_for_remote_command(name: str, cmd: RemoteCommand, template: str) -> str:
|
||||
template = '\n' + template[len('//go:build exclude'):]
|
||||
af: list[str] = []
|
||||
af: List[str] = []
|
||||
a = af.append
|
||||
af.extend(cmd.args.as_go_completion_code('ans'))
|
||||
od: list[str] = []
|
||||
option_map: dict[str, GoOption] = {}
|
||||
od: List[str] = []
|
||||
option_map: Dict[str, GoOption] = {}
|
||||
for o in rc_command_options(name):
|
||||
option_map[o.go_var_name] = o
|
||||
a(o.as_option('ans'))
|
||||
if o.go_var_name in ('NoResponse', 'ResponseTimeout'):
|
||||
continue
|
||||
od.append(o.struct_declaration())
|
||||
jd: list[str] = []
|
||||
jd: List[str] = []
|
||||
json_fields = []
|
||||
field_types: dict[str, str] = {}
|
||||
field_types: Dict[str, str] = {}
|
||||
for line in cmd.protocol_spec.splitlines():
|
||||
line = line.strip()
|
||||
if ':' not in line:
|
||||
continue
|
||||
f = JSONField(line, cmd.field_to_option_map or {}, option_map)
|
||||
f = JSONField(line)
|
||||
json_fields.append(f)
|
||||
field_types[f.field] = f.field_type
|
||||
jd.append(f.go_declaration())
|
||||
jc: list[str] = []
|
||||
handled_fields: set[str] = set()
|
||||
jc: List[str] = []
|
||||
handled_fields: Set[str] = set()
|
||||
jc.extend(cmd.args.as_go_code(name, field_types, handled_fields))
|
||||
|
||||
unhandled = {}
|
||||
used_options = set()
|
||||
for field in json_fields:
|
||||
if field.go_option_name in option_map:
|
||||
o = option_map[field.go_option_name]
|
||||
used_options.add(field.go_option_name)
|
||||
oq = (cmd.field_to_option_map or {}).get(field.field, field.field)
|
||||
oq = ''.join(x.capitalize() for x in oq.split('_'))
|
||||
if oq in option_map:
|
||||
o = option_map[oq]
|
||||
used_options.add(oq)
|
||||
optstring = f'options_{name}.{o.go_var_name}'
|
||||
if field.special_parser:
|
||||
optstring = f'{field.special_parser}({optstring})'
|
||||
@@ -427,7 +426,7 @@ def go_code_for_remote_command(name: str, cmd: RemoteCommand, template: str) ->
|
||||
# kittens {{{
|
||||
|
||||
@lru_cache
|
||||
def wrapped_kittens() -> tuple[str, ...]:
|
||||
def wrapped_kittens() -> Tuple[str, ...]:
|
||||
with open('shell-integration/ssh/kitty') as f:
|
||||
for line in f:
|
||||
if line.startswith(' wrapped_kittens="'):
|
||||
@@ -462,20 +461,9 @@ def generate_extra_cli_parser(name: str, spec: str) -> None:
|
||||
print('}')
|
||||
|
||||
|
||||
def kittens_needing_cli_parsers() -> Iterator[str]:
|
||||
for d in os.scandir('kittens'):
|
||||
if not d.is_dir(follow_symlinks=False):
|
||||
continue
|
||||
if os.path.exists(os.path.join(d.path, 'main.py')) and os.path.exists(os.path.join(d.path, 'main.go')):
|
||||
with open(os.path.join(d.path, 'main.py')) as f:
|
||||
raw = f.read()
|
||||
if 'options' in raw:
|
||||
yield d.name
|
||||
|
||||
|
||||
def kitten_clis() -> None:
|
||||
from kittens.runner import get_kitten_conf_docs, get_kitten_extra_cli_parsers
|
||||
for kitten in kittens_needing_cli_parsers():
|
||||
for kitten in wrapped_kittens() + ('pager',):
|
||||
defn = get_kitten_conf_docs(kitten)
|
||||
if defn is not None:
|
||||
generate_conf_parser(kitten, defn)
|
||||
@@ -572,7 +560,7 @@ SelectionBg: "{selbg}",
|
||||
'''
|
||||
|
||||
|
||||
def load_ref_map() -> dict[str, dict[str, str]]:
|
||||
def load_ref_map() -> Dict[str, Dict[str, str]]:
|
||||
with open('kitty/docs_ref_map_generated.h') as f:
|
||||
raw = f.read()
|
||||
raw = raw.split('{', 1)[1].split('}', 1)[0]
|
||||
@@ -583,7 +571,6 @@ 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
|
||||
@@ -628,7 +615,6 @@ 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 int
|
||||
@@ -639,9 +625,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}"
|
||||
''' # }}}
|
||||
|
||||
|
||||
@@ -657,7 +640,7 @@ def replace_if_needed(path: str, show_diff: bool = False) -> Iterator[io.StringI
|
||||
finally:
|
||||
sys.stdout = origb
|
||||
orig = ''
|
||||
with suppress(FileNotFoundError), open(path) as f:
|
||||
with suppress(FileNotFoundError), open(path, 'r') as f:
|
||||
orig = f.read()
|
||||
new = buf.getvalue()
|
||||
new = f'// Code generated by {os.path.basename(__file__)}; DO NOT EDIT.\n\n' + new
|
||||
@@ -673,7 +656,7 @@ def replace_if_needed(path: str, show_diff: bool = False) -> Iterator[io.StringI
|
||||
|
||||
|
||||
@lru_cache(maxsize=256)
|
||||
def rc_command_options(name: str) -> tuple[GoOption, ...]:
|
||||
def rc_command_options(name: str) -> Tuple[GoOption, ...]:
|
||||
cmd = command_for_name(name)
|
||||
return tuple(go_options_for_seq(parse_option_spec(cmd.options_spec or '\n\n')[0]))
|
||||
|
||||
@@ -883,7 +866,7 @@ def start_simdgen() -> 'subprocess.Popen[bytes]':
|
||||
return subprocess.Popen(['go', 'run', 'generate.go'], cwd='tools/simdstring', stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
|
||||
def main(args: list[str]=sys.argv) -> None:
|
||||
def main(args: List[str]=sys.argv) -> None:
|
||||
simdgen_process = start_simdgen()
|
||||
with replace_if_needed('constants_generated.go') as f:
|
||||
f.write(generate_constants())
|
||||
|
||||
@@ -6,7 +6,7 @@ import string
|
||||
import subprocess
|
||||
import sys
|
||||
from pprint import pformat
|
||||
from typing import Any, Union
|
||||
from typing import Any, Dict, List, Union
|
||||
|
||||
if __name__ == '__main__' and not __package__:
|
||||
import __main__
|
||||
@@ -194,11 +194,11 @@ macos_ansi_key_codes = { # {{{
|
||||
0x31: ord(' '),
|
||||
} # }}}
|
||||
|
||||
functional_key_names: list[str] = []
|
||||
name_to_code: dict[str, int] = {}
|
||||
name_to_xkb: dict[str, str] = {}
|
||||
name_to_vk: dict[str, int] = {}
|
||||
name_to_macu: dict[str, str] = {}
|
||||
functional_key_names: List[str] = []
|
||||
name_to_code: Dict[str, int] = {}
|
||||
name_to_xkb: Dict[str, str] = {}
|
||||
name_to_vk: Dict[str, int] = {}
|
||||
name_to_macu: Dict[str, str] = {}
|
||||
start_code = 0xe000
|
||||
for line in functional_key_defs.splitlines():
|
||||
line = line.strip()
|
||||
@@ -254,11 +254,11 @@ def patch_file(path: str, what: str, text: str, start_marker: str = '/* ', end_m
|
||||
subprocess.check_call(['go', 'fmt', path])
|
||||
|
||||
|
||||
def serialize_dict(x: dict[Any, Any]) -> str:
|
||||
def serialize_dict(x: Dict[Any, Any]) -> str:
|
||||
return pformat(x, indent=4).replace('{', '{\n ', 1)
|
||||
|
||||
|
||||
def serialize_go_dict(x: Union[dict[str, int], dict[int, str], dict[int, int]]) -> str:
|
||||
def serialize_go_dict(x: Union[Dict[str, int], Dict[int, str], Dict[int, int]]) -> str:
|
||||
ans = []
|
||||
|
||||
def s(x: Union[int, str]) -> str:
|
||||
@@ -332,7 +332,7 @@ def generate_functional_table() -> None:
|
||||
patch_file('kitty/key_encoding.c', 'special numbers', '\n'.join(enc_lines))
|
||||
code_to_name = {v: k.upper() for k, v in name_to_code.items()}
|
||||
csi_map = {v: name_to_code[k] for k, v in functional_encoding_overrides.items()}
|
||||
letter_trailer_codes: dict[str, int] = {
|
||||
letter_trailer_codes: Dict[str, int] = {
|
||||
v: functional_encoding_overrides.get(k, name_to_code.get(k, 0))
|
||||
for k, v in different_trailer_functionals.items() if v in 'ABCDEHFPQRSZ'}
|
||||
text = f'functional_key_number_to_name_map = {serialize_dict(code_to_name)}'
|
||||
@@ -377,7 +377,7 @@ def generate_legacy_text_key_maps() -> None:
|
||||
patch_file('kitty_tests/keys.py', 'legacy letter tests', '\n'.join(tests), start_marker='# ', end_marker='')
|
||||
|
||||
|
||||
def chunks(lst: list[Any], n: int) -> Any:
|
||||
def chunks(lst: List[Any], n: int) -> Any:
|
||||
"""Yield successive n-sized chunks from lst."""
|
||||
for i in range(0, len(lst), n):
|
||||
yield lst[i:i + n]
|
||||
@@ -427,7 +427,7 @@ def generate_macos_mapping() -> None:
|
||||
patch_file('glfw/cocoa_window.m', 'functional to macu', '\n'.join(lines))
|
||||
|
||||
|
||||
def main(args: list[str]=sys.argv) -> None:
|
||||
def main(args: List[str]=sys.argv) -> None:
|
||||
generate_glfw_header()
|
||||
generate_xkb_mapping()
|
||||
generate_functional_table()
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
|
||||
import os
|
||||
import sys
|
||||
from functools import lru_cache
|
||||
from typing import List
|
||||
|
||||
if __name__ == '__main__' and not __package__:
|
||||
import __main__
|
||||
@@ -18,12 +20,12 @@ def to_linear(a: float) -> float:
|
||||
|
||||
|
||||
@lru_cache
|
||||
def generate_srgb_lut(line_prefix: str = ' ') -> list[str]:
|
||||
values: list[str] = []
|
||||
lines: list[str] = []
|
||||
def generate_srgb_lut(line_prefix: str = ' ') -> List[str]:
|
||||
values: List[str] = []
|
||||
lines: List[str] = []
|
||||
|
||||
for i in range(256):
|
||||
values.append(f'{to_linear(i / 255.0):1.5f}f')
|
||||
values.append('{:1.5f}f'.format(to_linear(i / 255.0)))
|
||||
|
||||
for i in range(16):
|
||||
lines.append(line_prefix + ', '.join(values[i * 16:(i + 1) * 16]) + ',')
|
||||
@@ -33,7 +35,7 @@ def generate_srgb_lut(line_prefix: str = ' ') -> list[str]:
|
||||
|
||||
|
||||
def generate_srgb_gamma(declaration: str = 'static const GLfloat srgb_lut[256] = {', close: str = '};') -> str:
|
||||
lines: list[str] = []
|
||||
lines: List[str] = []
|
||||
a = lines.append
|
||||
|
||||
a('// Generated by gen-srgb-lut.py DO NOT edit')
|
||||
@@ -45,7 +47,7 @@ def generate_srgb_gamma(declaration: str = 'static const GLfloat srgb_lut[256] =
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main(args: list[str]=sys.argv) -> None:
|
||||
def main(args: List[str]=sys.argv) -> None:
|
||||
c = generate_srgb_gamma()
|
||||
with open(os.path.join('kitty', 'srgb_gamma.h'), 'w') as f:
|
||||
f.write(f'{c}\n')
|
||||
|
||||
@@ -6,7 +6,6 @@ import re
|
||||
import subprocess
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from collections.abc import Generator, Iterable
|
||||
from contextlib import contextmanager
|
||||
from functools import lru_cache, partial
|
||||
from html.entities import html5
|
||||
@@ -15,7 +14,14 @@ from operator import itemgetter
|
||||
from typing import (
|
||||
Callable,
|
||||
DefaultDict,
|
||||
Dict,
|
||||
FrozenSet,
|
||||
Generator,
|
||||
Iterable,
|
||||
List,
|
||||
Optional,
|
||||
Set,
|
||||
Tuple,
|
||||
Union,
|
||||
)
|
||||
from urllib.request import urlopen
|
||||
@@ -52,7 +58,7 @@ def get_data(fname: str, folder: str = 'UCD') -> Iterable[str]:
|
||||
|
||||
|
||||
@lru_cache(maxsize=2)
|
||||
def unicode_version() -> tuple[int, int, int]:
|
||||
def unicode_version() -> Tuple[int, int, int]:
|
||||
for line in get_data("ReadMe.txt"):
|
||||
m = re.search(r'Version\s+(\d+)\.(\d+)\.(\d+)', line)
|
||||
if m is not None:
|
||||
@@ -61,16 +67,16 @@ def unicode_version() -> tuple[int, int, int]:
|
||||
|
||||
|
||||
# Map of class names to set of codepoints in class
|
||||
class_maps: dict[str, set[int]] = {}
|
||||
all_symbols: set[int] = set()
|
||||
name_map: dict[int, str] = {}
|
||||
word_search_map: DefaultDict[str, set[int]] = defaultdict(set)
|
||||
class_maps: Dict[str, Set[int]] = {}
|
||||
all_symbols: Set[int] = set()
|
||||
name_map: Dict[int, str] = {}
|
||||
word_search_map: DefaultDict[str, Set[int]] = defaultdict(set)
|
||||
soft_hyphen = 0xad
|
||||
flag_codepoints = frozenset(range(0x1F1E6, 0x1F1E6 + 26))
|
||||
# See https://github.com/harfbuzz/harfbuzz/issues/169
|
||||
marks = set(emoji_skin_tone_modifiers) | flag_codepoints
|
||||
not_assigned = set(range(0, sys.maxunicode))
|
||||
property_maps: dict[str, set[int]] = defaultdict(set)
|
||||
property_maps: Dict[str, Set[int]] = defaultdict(set)
|
||||
|
||||
|
||||
def parse_prop_list() -> None:
|
||||
@@ -112,7 +118,7 @@ def parse_ucd() -> None:
|
||||
category = parts[2]
|
||||
s = class_maps.setdefault(category, set())
|
||||
desc = parts[1]
|
||||
codepoints: Union[tuple[int, ...], Iterable[int]] = (codepoint,)
|
||||
codepoints: Union[Tuple[int, ...], Iterable[int]] = (codepoint,)
|
||||
if first is None:
|
||||
if desc.endswith(', First>'):
|
||||
first = codepoint
|
||||
@@ -153,7 +159,7 @@ def parse_ucd() -> None:
|
||||
word_search_map['diamond'] |= word_search_map['gem']
|
||||
|
||||
|
||||
def parse_range_spec(spec: str) -> set[int]:
|
||||
def parse_range_spec(spec: str) -> Set[int]:
|
||||
spec = spec.strip()
|
||||
if '..' in spec:
|
||||
chars_ = tuple(map(lambda x: int(x, 16), filter(None, spec.split('.'))))
|
||||
@@ -163,17 +169,17 @@ def parse_range_spec(spec: str) -> set[int]:
|
||||
return chars
|
||||
|
||||
|
||||
def split_two(line: str) -> tuple[set[int], str]:
|
||||
def split_two(line: str) -> Tuple[Set[int], str]:
|
||||
spec, rest = line.split(';', 1)
|
||||
spec, rest = spec.strip(), rest.strip().split(' ', 1)[0].strip()
|
||||
return parse_range_spec(spec), rest
|
||||
|
||||
|
||||
all_emoji: set[int] = set()
|
||||
emoji_presentation_bases: set[int] = set()
|
||||
narrow_emoji: set[int] = set()
|
||||
wide_emoji: set[int] = set()
|
||||
flags: dict[int, list[int]] = {}
|
||||
all_emoji: Set[int] = set()
|
||||
emoji_presentation_bases: Set[int] = set()
|
||||
narrow_emoji: Set[int] = set()
|
||||
wide_emoji: Set[int] = set()
|
||||
flags: Dict[int, List[int]] = {}
|
||||
|
||||
|
||||
def parse_basic_emoji(spec: str) -> None:
|
||||
@@ -237,13 +243,13 @@ def parse_emoji() -> None:
|
||||
parse_emoji_modifier_sequence(data)
|
||||
|
||||
|
||||
doublewidth: set[int] = set()
|
||||
ambiguous: set[int] = set()
|
||||
doublewidth: Set[int] = set()
|
||||
ambiguous: Set[int] = set()
|
||||
|
||||
|
||||
def parse_eaw() -> None:
|
||||
global doublewidth, ambiguous
|
||||
seen: set[int] = set()
|
||||
seen: Set[int] = set()
|
||||
for line in get_data('ucd/EastAsianWidth.txt'):
|
||||
chars, eaw = split_two(line)
|
||||
if eaw == 'A':
|
||||
@@ -259,7 +265,7 @@ def parse_eaw() -> None:
|
||||
doublewidth |= set(range(0x30000, 0x3FFFD + 1)) - seen
|
||||
|
||||
|
||||
def get_ranges(items: list[int]) -> Generator[Union[int, tuple[int, int]], None, None]:
|
||||
def get_ranges(items: List[int]) -> Generator[Union[int, Tuple[int, int]], None, None]:
|
||||
items.sort()
|
||||
for k, g in groupby(enumerate(items), lambda m: m[0]-m[1]):
|
||||
group = tuple(map(itemgetter(1), g))
|
||||
@@ -270,7 +276,7 @@ def get_ranges(items: list[int]) -> Generator[Union[int, tuple[int, int]], None,
|
||||
yield a, b
|
||||
|
||||
|
||||
def write_case(spec: Union[tuple[int, ...], int], p: Callable[..., None], for_go: bool = False) -> None:
|
||||
def write_case(spec: Union[Tuple[int, ...], int], p: Callable[..., None], for_go: bool = False) -> None:
|
||||
if isinstance(spec, tuple):
|
||||
if for_go:
|
||||
v = ', '.join(f'0x{x:x}' for x in range(spec[0], spec[1] + 1))
|
||||
@@ -326,13 +332,13 @@ def category_test(
|
||||
classes: Iterable[str],
|
||||
comment: str,
|
||||
use_static: bool = False,
|
||||
extra_chars: Union[frozenset[int], set[int]] = frozenset(),
|
||||
exclude: Union[set[int], frozenset[int]] = frozenset(),
|
||||
extra_chars: Union[FrozenSet[int], Set[int]] = frozenset(),
|
||||
exclude: Union[Set[int], FrozenSet[int]] = frozenset(),
|
||||
least_check_return: Optional[str] = None,
|
||||
ascii_range: Optional[str] = None
|
||||
) -> None:
|
||||
static = 'static inline ' if use_static else ''
|
||||
chars: set[int] = set()
|
||||
chars: Set[int] = set()
|
||||
for c in classes:
|
||||
chars |= class_maps[c]
|
||||
chars |= extra_chars
|
||||
@@ -352,7 +358,7 @@ def category_test(
|
||||
p('\treturn false;\n}\n')
|
||||
|
||||
|
||||
def codepoint_to_mark_map(p: Callable[..., None], mark_map: list[int]) -> dict[int, int]:
|
||||
def codepoint_to_mark_map(p: Callable[..., None], mark_map: List[int]) -> Dict[int, int]:
|
||||
p('\tswitch(c) { // {{{')
|
||||
rmap = {c: m for m, c in enumerate(mark_map)}
|
||||
for spec in get_ranges(mark_map):
|
||||
@@ -368,7 +374,7 @@ def codepoint_to_mark_map(p: Callable[..., None], mark_map: list[int]) -> dict[i
|
||||
|
||||
|
||||
def classes_to_regex(classes: Iterable[str], exclude: str = '', for_go: bool = True) -> Iterable[str]:
|
||||
chars: set[int] = set()
|
||||
chars: Set[int] = set()
|
||||
for c in classes:
|
||||
chars |= class_maps[c]
|
||||
for x in map(ord, exclude):
|
||||
@@ -421,9 +427,31 @@ def gen_ucd() -> None:
|
||||
category_test('is_word_char', p, {c for c in class_maps if c[0] in 'LN'}, 'L and N categories')
|
||||
category_test('is_CZ_category', p, cz, 'C and Z categories')
|
||||
category_test('is_P_category', p, {c for c in class_maps if c[0] == 'P'}, 'P category (punctuation)')
|
||||
mark_map = [0] + list(sorted(marks))
|
||||
p('char_type codepoint_for_mark(combining_type m) {')
|
||||
p(f'\tstatic char_type map[{len(mark_map)}] =', '{', ', '.join(map(str, mark_map)), '}; // {{{ mapping }}}')
|
||||
p('\tif (m < arraysz(map)) return map[m];')
|
||||
p('\treturn 0;')
|
||||
p('}\n')
|
||||
p('combining_type mark_for_codepoint(char_type c) {')
|
||||
rmap = codepoint_to_mark_map(p, mark_map)
|
||||
p('}\n')
|
||||
with open('kitty/unicode-data.h', 'r+') as f:
|
||||
raw = f.read()
|
||||
f.seek(0)
|
||||
raw, num = re.subn(
|
||||
r'^// START_KNOWN_MARKS.+?^// END_KNOWN_MARKS',
|
||||
'// START_KNOWN_MARKS\nstatic const combining_type '
|
||||
f'VS15 = {rmap[0xfe0e]}, VS16 = {rmap[0xfe0f]};'
|
||||
'\n// END_KNOWN_MARKS', raw, flags=re.MULTILINE | re.DOTALL)
|
||||
if not num:
|
||||
raise SystemExit('Faile dto patch mark definitions in unicode-data.h')
|
||||
f.truncate()
|
||||
f.write(raw)
|
||||
|
||||
|
||||
def gen_names() -> None:
|
||||
aliases_map: dict[int, set[str]] = {}
|
||||
aliases_map: Dict[int, Set[str]] = {}
|
||||
for word, codepoints in word_search_map.items():
|
||||
for cp in codepoints:
|
||||
aliases_map.setdefault(cp, set()).add(word)
|
||||
@@ -442,10 +470,10 @@ def gen_names() -> None:
|
||||
|
||||
|
||||
def gen_wcwidth() -> None:
|
||||
seen: set[int] = set()
|
||||
seen: Set[int] = set()
|
||||
non_printing = class_maps['Cc'] | class_maps['Cf'] | class_maps['Cs']
|
||||
|
||||
def add(p: Callable[..., None], comment: str, chars_: Union[set[int], frozenset[int]], ret: int, for_go: bool = False) -> None:
|
||||
def add(p: Callable[..., None], comment: str, chars_: Union[Set[int], FrozenSet[int]], ret: int, for_go: bool = False) -> None:
|
||||
chars = chars_ - seen
|
||||
seen.update(chars)
|
||||
p(f'\t\t// {comment} ({len(chars)} codepoints)' + ' {{' '{')
|
||||
@@ -554,7 +582,7 @@ def gen_rowcolumn_diacritics() -> None:
|
||||
subprocess.check_call(['gofmt', '-w', '-s', go_file])
|
||||
|
||||
|
||||
def main(args: list[str]=sys.argv) -> None:
|
||||
def main(args: List[str]=sys.argv) -> None:
|
||||
parse_ucd()
|
||||
parse_prop_list()
|
||||
parse_emoji()
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
/*
|
||||
* cocoa_displaylink.m
|
||||
* Copyright (C) 2024 Kovid Goyal <kovid at kovidgoyal.net>
|
||||
*
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
|
||||
// CVDisplayLink is deprecated replace with CADisplayLink via [NSScreen displayLink] once base macOS version is 14
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
|
||||
#include "internal.h"
|
||||
#include <CoreVideo/CVDisplayLink.h>
|
||||
|
||||
#define DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL s_to_monotonic_t(30ll)
|
||||
|
||||
typedef struct _GLFWDisplayLinkNS
|
||||
{
|
||||
CVDisplayLinkRef displayLink;
|
||||
CGDirectDisplayID displayID;
|
||||
monotonic_t lastRenderFrameRequestedAt, first_unserviced_render_frame_request_at;
|
||||
} _GLFWDisplayLinkNS;
|
||||
|
||||
static struct {
|
||||
_GLFWDisplayLinkNS entries[256];
|
||||
size_t count;
|
||||
} displayLinks = {0};
|
||||
|
||||
static CGDirectDisplayID
|
||||
displayIDForWindow(_GLFWwindow *w) {
|
||||
NSWindow *nw = w->ns.object;
|
||||
NSDictionary *dict = [nw.screen deviceDescription];
|
||||
NSNumber *displayIDns = dict[@"NSScreenNumber"];
|
||||
if (displayIDns) return [displayIDns unsignedIntValue];
|
||||
return (CGDirectDisplayID)-1;
|
||||
}
|
||||
|
||||
void
|
||||
_glfwClearDisplayLinks(void) {
|
||||
for (size_t i = 0; i < displayLinks.count; i++) {
|
||||
if (displayLinks.entries[i].displayLink) {
|
||||
CVDisplayLinkStop(displayLinks.entries[i].displayLink);
|
||||
CVDisplayLinkRelease(displayLinks.entries[i].displayLink);
|
||||
}
|
||||
}
|
||||
memset(displayLinks.entries, 0, sizeof(_GLFWDisplayLinkNS) * displayLinks.count);
|
||||
displayLinks.count = 0;
|
||||
}
|
||||
|
||||
static CVReturn
|
||||
displayLinkCallback(
|
||||
CVDisplayLinkRef displayLink UNUSED,
|
||||
const CVTimeStamp* now UNUSED, const CVTimeStamp* outputTime UNUSED,
|
||||
CVOptionFlags flagsIn UNUSED, CVOptionFlags* flagsOut UNUSED, void* userInfo) {
|
||||
CGDirectDisplayID displayID = (uintptr_t)userInfo;
|
||||
NSNumber *arg = [NSNumber numberWithUnsignedInt:displayID];
|
||||
[NSApp performSelectorOnMainThread:@selector(render_frame_received:) withObject:arg waitUntilDone:NO];
|
||||
[arg release];
|
||||
return kCVReturnSuccess;
|
||||
}
|
||||
|
||||
static void
|
||||
_glfw_create_cv_display_link(_GLFWDisplayLinkNS *entry) {
|
||||
CVDisplayLinkCreateWithCGDisplay(entry->displayID, &entry->displayLink);
|
||||
CVDisplayLinkSetOutputCallback(entry->displayLink, &displayLinkCallback, (void*)(uintptr_t)entry->displayID);
|
||||
}
|
||||
|
||||
unsigned
|
||||
_glfwCreateDisplayLink(CGDirectDisplayID displayID) {
|
||||
if (displayLinks.count >= arraysz(displayLinks.entries) - 1) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Too many monitors cannot create display link");
|
||||
return displayLinks.count;
|
||||
}
|
||||
for (unsigned i = 0; i < displayLinks.count; i++) {
|
||||
// already created in this run
|
||||
if (displayLinks.entries[i].displayID == displayID) return i;
|
||||
}
|
||||
_GLFWDisplayLinkNS *entry = &displayLinks.entries[displayLinks.count++];
|
||||
memset(entry, 0, sizeof(_GLFWDisplayLinkNS));
|
||||
entry->displayID = displayID;
|
||||
_glfw_create_cv_display_link(entry);
|
||||
return displayLinks.count - 1;
|
||||
}
|
||||
|
||||
static unsigned long long display_link_shutdown_timer = 0;
|
||||
|
||||
static void
|
||||
_glfwShutdownCVDisplayLink(unsigned long long timer_id UNUSED, void *user_data UNUSED) {
|
||||
display_link_shutdown_timer = 0;
|
||||
for (size_t i = 0; i < displayLinks.count; i++) {
|
||||
_GLFWDisplayLinkNS *dl = &displayLinks.entries[i];
|
||||
if (dl->displayLink) CVDisplayLinkStop(dl->displayLink);
|
||||
dl->lastRenderFrameRequestedAt = 0;
|
||||
dl->first_unserviced_render_frame_request_at = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
_glfwRequestRenderFrame(_GLFWwindow *w) {
|
||||
CGDirectDisplayID displayID = displayIDForWindow(w);
|
||||
if (display_link_shutdown_timer) {
|
||||
_glfwPlatformUpdateTimer(display_link_shutdown_timer, DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL, true);
|
||||
} else {
|
||||
display_link_shutdown_timer = _glfwPlatformAddTimer(DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL, false, _glfwShutdownCVDisplayLink, NULL, NULL);
|
||||
}
|
||||
monotonic_t now = glfwGetTime();
|
||||
bool found_display_link = false;
|
||||
_GLFWDisplayLinkNS *dl = NULL;
|
||||
for (size_t i = 0; i < displayLinks.count; i++) {
|
||||
dl = &displayLinks.entries[i];
|
||||
if (dl->displayID == displayID) {
|
||||
found_display_link = true;
|
||||
dl->lastRenderFrameRequestedAt = now;
|
||||
if (!dl->first_unserviced_render_frame_request_at) dl->first_unserviced_render_frame_request_at = now;
|
||||
if (!CVDisplayLinkIsRunning(dl->displayLink)) CVDisplayLinkStart(dl->displayLink);
|
||||
else if (now - dl->first_unserviced_render_frame_request_at > s_to_monotonic_t(1ll)) {
|
||||
// display link is stuck need to recreate it because Apple can't even
|
||||
// get a simple timer right
|
||||
CVDisplayLinkRelease(dl->displayLink); dl->displayLink = nil;
|
||||
dl->first_unserviced_render_frame_request_at = now;
|
||||
_glfw_create_cv_display_link(dl);
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"CVDisplayLink stuck possibly because of sleep/screensaver + Apple's incompetence, recreating.");
|
||||
if (!CVDisplayLinkIsRunning(dl->displayLink)) CVDisplayLinkStart(dl->displayLink);
|
||||
}
|
||||
} else if (dl->displayLink && dl->lastRenderFrameRequestedAt && now - dl->lastRenderFrameRequestedAt >= DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL) {
|
||||
CVDisplayLinkStop(dl->displayLink);
|
||||
dl->lastRenderFrameRequestedAt = 0;
|
||||
dl->first_unserviced_render_frame_request_at = 0;
|
||||
}
|
||||
}
|
||||
if (!found_display_link) {
|
||||
unsigned idx = _glfwCreateDisplayLink(displayID);
|
||||
if (idx < displayLinks.count) {
|
||||
dl = &displayLinks.entries[idx];
|
||||
dl->lastRenderFrameRequestedAt = now;
|
||||
dl->first_unserviced_render_frame_request_at = now;
|
||||
if (!CVDisplayLinkIsRunning(dl->displayLink)) CVDisplayLinkStart(dl->displayLink);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
_glfwDispatchRenderFrame(CGDirectDisplayID displayID) {
|
||||
_GLFWwindow *w = _glfw.windowListHead;
|
||||
while (w) {
|
||||
if (w->ns.renderFrameRequested && displayID == displayIDForWindow(w)) {
|
||||
w->ns.renderFrameRequested = false;
|
||||
w->ns.renderFrameCallback((GLFWwindow*)w);
|
||||
}
|
||||
w = w->next;
|
||||
}
|
||||
for (size_t i = 0; i < displayLinks.count; i++) {
|
||||
_GLFWDisplayLinkNS *dl = &displayLinks.entries[i];
|
||||
if (dl->displayID == displayID) {
|
||||
dl->first_unserviced_render_frame_request_at = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -579,7 +579,7 @@ is_shiftable_shortcut(int scv) {
|
||||
return scv == kSHKMoveFocusToActiveOrNextWindow || scv == kSHKMoveFocusToNextWindow;
|
||||
}
|
||||
|
||||
#define USEFUL_MODS(x) (x & (NSEventModifierFlagShift | NSEventModifierFlagOption | NSEventModifierFlagCommand | NSEventModifierFlagControl | NSEventModifierFlagFunction))
|
||||
#define USEFUL_MODS(x) (x & (NSEventModifierFlagShift | NSEventModifierFlagOption | NSEventModifierFlagCommand | NSEventModifierFlagControl))
|
||||
|
||||
static void
|
||||
build_global_shortcuts_lookup(void) {
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
|
||||
#include <IOKit/graphics/IOGraphicsLib.h>
|
||||
#include <CoreVideo/CVBase.h>
|
||||
#include <CoreVideo/CVDisplayLink.h>
|
||||
#include <ApplicationServices/ApplicationServices.h>
|
||||
|
||||
|
||||
@@ -322,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;
|
||||
@@ -378,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;
|
||||
}
|
||||
@@ -399,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);
|
||||
|
||||
|
||||
25
glfw/cocoa_platform.h
vendored
25
glfw/cocoa_platform.h
vendored
@@ -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
|
||||
@@ -158,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
|
||||
@@ -190,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;
|
||||
|
||||
@@ -231,16 +244,12 @@ float _glfwTransformYNS(float y);
|
||||
|
||||
void* _glfwLoadLocalVulkanLoaderNS(void);
|
||||
|
||||
|
||||
// display links
|
||||
void _glfwClearDisplayLinks(void);
|
||||
void _glfwRestartDisplayLinks(void);
|
||||
unsigned _glfwCreateDisplayLink(CGDirectDisplayID);
|
||||
void _glfwDispatchRenderFrame(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);
|
||||
|
||||
@@ -306,6 +306,28 @@ static NSUInteger getStyleMask(_GLFWwindow* window)
|
||||
}
|
||||
|
||||
|
||||
CGDirectDisplayID displayIDForWindow(_GLFWwindow *w) {
|
||||
NSWindow *nw = w->ns.object;
|
||||
NSDictionary *dict = [nw.screen deviceDescription];
|
||||
NSNumber *displayIDns = dict[@"NSScreenNumber"];
|
||||
if (displayIDns) return [displayIDns unsignedIntValue];
|
||||
return (CGDirectDisplayID)-1;
|
||||
}
|
||||
|
||||
static unsigned long long display_link_shutdown_timer = 0;
|
||||
#define DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL s_to_monotonic_t(30ll)
|
||||
|
||||
void
|
||||
_glfwShutdownCVDisplayLink(unsigned long long timer_id UNUSED, void *user_data UNUSED) {
|
||||
display_link_shutdown_timer = 0;
|
||||
for (size_t i = 0; i < _glfw.ns.displayLinks.count; i++) {
|
||||
_GLFWDisplayLinkNS *dl = &_glfw.ns.displayLinks.entries[i];
|
||||
if (dl->displayLink) CVDisplayLinkStop(dl->displayLink);
|
||||
dl->lastRenderFrameRequestedAt = 0;
|
||||
dl->first_unserviced_render_frame_request_at = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
requestRenderFrame(_GLFWwindow *w, GLFWcocoarenderframefun callback) {
|
||||
if (!callback) {
|
||||
@@ -315,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
|
||||
@@ -940,7 +1001,6 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
||||
(void)event;
|
||||
if (!window) return;
|
||||
_glfwInputCursorEnter(window, false);
|
||||
[[NSCursor arrowCursor] set];
|
||||
}
|
||||
|
||||
- (void)mouseEntered:(NSEvent *)event
|
||||
@@ -948,16 +1008,15 @@ 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(true);
|
||||
GLFWColorScheme new_appearance = glfwGetCurrentSystemColorTheme();
|
||||
if (new_appearance != appearance) {
|
||||
appearance = new_appearance;
|
||||
_glfwInputColorScheme(appearance, false);
|
||||
_glfwInputColorScheme(appearance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2278,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];
|
||||
@@ -3093,8 +3170,7 @@ GLFWAPI void glfwCocoaSetWindowChrome(GLFWwindow *w, unsigned int color, bool us
|
||||
[window->ns.object makeFirstResponder:window->ns.view];
|
||||
}}
|
||||
|
||||
GLFWAPI GLFWColorScheme glfwGetCurrentSystemColorTheme(bool query_if_unintialized) {
|
||||
(void)query_if_unintialized;
|
||||
GLFWAPI GLFWColorScheme glfwGetCurrentSystemColorTheme(void) {
|
||||
int theme_type = 0;
|
||||
NSAppearance *changedAppearance = NSApp.effectiveAppearance;
|
||||
NSAppearanceName newAppearance = [changedAppearance bestMatchFromAppearancesWithNames:@[NSAppearanceNameAqua, NSAppearanceNameDarkAqua]];
|
||||
|
||||
31
glfw/dbus_glfw.c
vendored
31
glfw/dbus_glfw.c
vendored
@@ -234,21 +234,12 @@ method_reply_received(DBusPendingCall *pending, void *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.message, user_data);
|
||||
return false;
|
||||
} else if (reply) {
|
||||
callback(reply, NULL, user_data);
|
||||
} else return false;
|
||||
} else if (dbus_connection_send_with_reply(conn, msg, &pending, timeout)) {
|
||||
if (dbus_connection_send_with_reply(conn, msg, &pending, timeout)) {
|
||||
MethodResponse *res = malloc(sizeof(MethodResponse));
|
||||
if (!res) return false;
|
||||
res->callback = callback;
|
||||
@@ -270,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;
|
||||
@@ -278,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);
|
||||
}
|
||||
@@ -291,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;
|
||||
}
|
||||
@@ -311,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;
|
||||
}
|
||||
|
||||
4
glfw/dbus_glfw.h
vendored
4
glfw/dbus_glfw.h
vendored
@@ -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, ...);
|
||||
|
||||
@@ -323,9 +323,10 @@ 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)
|
||||
void glfwWaylandSetupLayerShellForNextWindow(const GLFWLayerShellConfig *c)
|
||||
void glfwWaylandSetupLayerShellForNextWindow(GLFWLayerShellConfig c)
|
||||
pid_t glfwWaylandCompositorPID(void)
|
||||
unsigned long long glfwDBusUserNotify(const GLFWDBUSNotificationData *n, GLFWDBusnotificationcreatedfun callback, void *data)
|
||||
unsigned long long glfwDBusUserNotify(const char *app_name, const char* icon, const char *summary, const char *body, \
|
||||
const char *action_text, int32_t timeout, int urgency, GLFWDBusnotificationcreatedfun callback, void *data)
|
||||
void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler)
|
||||
int glfwSetX11LaunchCommand(GLFWwindow *handle, char **argv, int argc)
|
||||
void glfwSetX11WindowAsDock(int32_t x11_window_id)
|
||||
@@ -363,7 +364,7 @@ typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
|
||||
typedef void (* GLFWcocoarenderframefun)(GLFWwindow*);
|
||||
typedef void (*GLFWwaylandframecallbackfunc)(unsigned long long id);
|
||||
typedef void (*GLFWDBusnotificationcreatedfun)(unsigned long long, uint32_t, void*);
|
||||
typedef void (*GLFWDBusnotificationactivatedfun)(uint32_t, int, const char*);
|
||||
typedef void (*GLFWDBusnotificationactivatedfun)(uint32_t, const char*);
|
||||
{}
|
||||
|
||||
const char* load_glfw(const char* path);
|
||||
|
||||
27
glfw/glfw3.h
vendored
27
glfw/glfw3.h
vendored
@@ -1300,33 +1300,21 @@ 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_NONE } GLFWEdge;
|
||||
typedef enum { GLFW_EDGE_TOP, GLFW_EDGE_BOTTOM, GLFW_EDGE_LEFT, GLFW_EDGE_RIGHT } GLFWEdge;
|
||||
|
||||
typedef enum { GLFW_FOCUS_NOT_ALLOWED, GLFW_FOCUS_EXCLUSIVE, GLFW_FOCUS_ON_DEMAND} GLFWFocusPolicy;
|
||||
|
||||
typedef struct GLFWLayerShellConfig {
|
||||
GLFWLayerShellType type;
|
||||
GLFWEdge edge;
|
||||
char output_name[64];
|
||||
const char *output_name;
|
||||
GLFWFocusPolicy focus_policy;
|
||||
unsigned x_size_in_cells;
|
||||
unsigned y_size_in_cells;
|
||||
unsigned requested_top_margin;
|
||||
unsigned requested_left_margin;
|
||||
unsigned requested_bottom_margin;
|
||||
unsigned requested_right_margin;
|
||||
int requested_exclusive_zone;
|
||||
unsigned override_exclusive_zone;
|
||||
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 {
|
||||
const char *app_name, *icon, *summary, *body, *category, **actions; size_t num_actions;
|
||||
int32_t timeout; uint8_t urgency; uint32_t replaces; int muted;
|
||||
} GLFWDBUSNotificationData;
|
||||
|
||||
/*! @brief The function pointer type for error callbacks.
|
||||
*
|
||||
* This is the function pointer type for error callbacks. An error callback
|
||||
@@ -1436,17 +1424,16 @@ typedef void (* GLFWapplicationclosefun)(int);
|
||||
*
|
||||
* 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.
|
||||
@@ -3989,7 +3976,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 GLFWColorScheme glfwGetCurrentSystemColorTheme(bool query_if_unintialized);
|
||||
GLFWAPI GLFWColorScheme glfwGetCurrentSystemColorTheme(void);
|
||||
|
||||
/*! @brief Sets the refresh callback for the specified window.
|
||||
*
|
||||
|
||||
7
glfw/ibus_glfw.c
vendored
7
glfw/ibus_glfw.c
vendored
@@ -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;
|
||||
|
||||
4
glfw/input.c
vendored
4
glfw/input.c
vendored
@@ -448,9 +448,9 @@ 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);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
2
glfw/internal.h
vendored
2
glfw/internal.h
vendored
@@ -814,7 +814,7 @@ 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);
|
||||
|
||||
55
glfw/linux_desktop_settings.c
vendored
55
glfw/linux_desktop_settings.c
vendored
@@ -15,14 +15,18 @@
|
||||
#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;
|
||||
static bool cursor_theme_changed = false;
|
||||
|
||||
GLFWColorScheme
|
||||
glfw_current_system_color_theme(void) {
|
||||
return appearance;
|
||||
}
|
||||
|
||||
#define HANDLER(name) static void name(DBusMessage *msg, const char* errmsg, void *data) { \
|
||||
(void)data; \
|
||||
@@ -31,34 +35,6 @@ static bool cursor_theme_changed = false, appearance_initialized = false;
|
||||
return; \
|
||||
}
|
||||
|
||||
|
||||
HANDLER(get_color_scheme)
|
||||
uint32_t val;
|
||||
DBusMessageIter iter, variant_iter;
|
||||
if (!dbus_message_iter_init(msg, &iter)) return;
|
||||
dbus_message_iter_recurse(&iter, &variant_iter);
|
||||
int type = dbus_message_iter_get_arg_type(&variant_iter);
|
||||
if (type != DBUS_TYPE_UINT32) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "ReadOne for color-scheme did not return a uint32"); return;
|
||||
}
|
||||
dbus_message_iter_get_basic(&variant_iter, &val);
|
||||
if (val < 3) appearance = val;
|
||||
}
|
||||
|
||||
GLFWColorScheme
|
||||
glfw_current_system_color_theme(bool query_if_unintialized) {
|
||||
if (!appearance_initialized && query_if_unintialized) {
|
||||
appearance_initialized = true;
|
||||
DBusConnection *session_bus = glfw_dbus_session_bus();
|
||||
if (session_bus) {
|
||||
const char *namespace = FDO_DESKTOP_NAMESPACE, *key = FDO_APPEARANCE_KEY;
|
||||
glfw_dbus_call_blocking_method(session_bus, DESKTOP_SERVICE, DESKTOP_PATH, DESKTOP_INTERFACE, "ReadOne", DBUS_TIMEOUT_USE_DEFAULT,
|
||||
get_color_scheme, NULL, DBUS_TYPE_STRING, &namespace, DBUS_TYPE_STRING, &key, DBUS_TYPE_INVALID);
|
||||
}
|
||||
}
|
||||
return appearance;
|
||||
}
|
||||
|
||||
static void
|
||||
process_fdo_setting(const char *key, DBusMessageIter *value) {
|
||||
if (strcmp(key, FDO_APPEARANCE_KEY) == 0) {
|
||||
@@ -66,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,9 +111,7 @@ HANDLER(process_desktop_settings)
|
||||
if (!dbus_message_iter_next(&array)) break;
|
||||
}
|
||||
#undef die
|
||||
#ifndef _GLFW_X11
|
||||
if (cursor_theme_changed) _glfwPlatformChangeCursorTheme();
|
||||
#endif
|
||||
}
|
||||
|
||||
#undef HANDLER
|
||||
@@ -155,11 +123,9 @@ 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);
|
||||
bool ok = call_method_with_msg(session_bus, msg, DBUS_TIMEOUT_USE_DEFAULT, process_desktop_settings, NULL);
|
||||
return ok;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -194,8 +160,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;
|
||||
|
||||
2
glfw/linux_desktop_settings.h
vendored
2
glfw/linux_desktop_settings.h
vendored
@@ -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);
|
||||
|
||||
128
glfw/linux_notify.c
vendored
128
glfw/linux_notify.c
vendored
@@ -5,11 +5,9 @@
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "internal.h"
|
||||
#include "linux_notify.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define NOTIFICATIONS_SERVICE "org.freedesktop.Notifications"
|
||||
#define NOTIFICATIONS_PATH "/org/freedesktop/Notifications"
|
||||
@@ -18,6 +16,8 @@
|
||||
static inline void cleanup_free(void *p) { free(*(void**)p); }
|
||||
#define RAII_ALLOC(type, name, initializer) __attribute__((cleanup(cleanup_free))) type *name = initializer
|
||||
|
||||
static notification_id_type notification_id = 0;
|
||||
|
||||
typedef struct {
|
||||
notification_id_type next_id;
|
||||
GLFWDBusnotificationcreatedfun callback;
|
||||
@@ -41,10 +41,8 @@ notification_created(DBusMessage *msg, const char* errmsg, void *data) {
|
||||
uint32_t id;
|
||||
if (!glfw_dbus_get_args(msg, "Failed to get Notification uid", DBUS_TYPE_UINT32, &id, DBUS_TYPE_INVALID)) return;
|
||||
NotificationCreatedData *ncd = (NotificationCreatedData*)data;
|
||||
if (ncd) {
|
||||
if (ncd->callback) ncd->callback(ncd->next_id, id, ncd->data);
|
||||
free(ncd);
|
||||
}
|
||||
if (ncd->callback) ncd->callback(ncd->next_id, id, ncd->data);
|
||||
if (data) free(data);
|
||||
}
|
||||
|
||||
static DBusHandlerResult
|
||||
@@ -52,100 +50,35 @@ message_handler(DBusConnection *conn UNUSED, DBusMessage *msg, void *user_data U
|
||||
/* printf("session_bus message_handler invoked interface: %s member: %s\n", dbus_message_get_interface(msg), dbus_message_get_member(msg)); */
|
||||
if (dbus_message_is_signal(msg, NOTIFICATIONS_IFACE, "ActionInvoked")) {
|
||||
uint32_t id;
|
||||
const char *action = NULL;
|
||||
const char *action;
|
||||
if (glfw_dbus_get_args(msg, "Failed to get args from ActionInvoked notification signal",
|
||||
DBUS_TYPE_UINT32, &id, DBUS_TYPE_STRING, &action, DBUS_TYPE_INVALID)) {
|
||||
if (activated_handler) {
|
||||
activated_handler(id, 2, action);
|
||||
activated_handler(id, action);
|
||||
return DBUS_HANDLER_RESULT_HANDLED;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (dbus_message_is_signal(msg, NOTIFICATIONS_IFACE, "ActivationToken")) {
|
||||
uint32_t id;
|
||||
const char *token = NULL;
|
||||
if (glfw_dbus_get_args(msg, "Failed to get args from ActivationToken notification signal",
|
||||
DBUS_TYPE_UINT32, &id, DBUS_TYPE_STRING, &token, DBUS_TYPE_INVALID)) {
|
||||
if (activated_handler) {
|
||||
activated_handler(id, 1, token);
|
||||
return DBUS_HANDLER_RESULT_HANDLED;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (dbus_message_is_signal(msg, NOTIFICATIONS_IFACE, "NotificationClosed")) {
|
||||
uint32_t id;
|
||||
if (glfw_dbus_get_args(msg, "Failed to get args from NotificationClosed notification signal",
|
||||
DBUS_TYPE_UINT32, &id, DBUS_TYPE_INVALID)) {
|
||||
if (activated_handler) {
|
||||
activated_handler(id, 0, "");
|
||||
return DBUS_HANDLER_RESULT_HANDLED;
|
||||
}
|
||||
}
|
||||
}
|
||||
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
||||
}
|
||||
|
||||
static bool
|
||||
cancel_user_notification(DBusConnection *session_bus, uint32_t *id) {
|
||||
return glfw_dbus_call_method_no_reply(session_bus, NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE, "CloseNotification", DBUS_TYPE_UINT32, id, DBUS_TYPE_INVALID);
|
||||
}
|
||||
|
||||
static void
|
||||
got_capabilities(DBusMessage *msg, const char* err, void* data UNUSED) {
|
||||
if (err) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Notify: Failed to get server capabilities error: %s", err);
|
||||
return;
|
||||
}
|
||||
#define check_call(func, err, ...) if (!func(__VA_ARGS__)) { _glfwInputError(GLFW_PLATFORM_ERROR, "Notify: GetCapabilities: %s", err); return; }
|
||||
DBusMessageIter iter, array_iter;
|
||||
check_call(dbus_message_iter_init, "message has no parameters", msg, &iter);
|
||||
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRING) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Notify: GetCapabilities: %s", "reply is not an array of strings");
|
||||
return;
|
||||
}
|
||||
dbus_message_iter_recurse(&iter, &array_iter);
|
||||
char buf[2048] = {0}, *p = buf, *end = buf + sizeof(buf);
|
||||
while (dbus_message_iter_get_arg_type(&array_iter) == DBUS_TYPE_STRING) {
|
||||
const char *str;
|
||||
dbus_message_iter_get_basic(&array_iter, &str);
|
||||
size_t len = strlen(str);
|
||||
if (len && p + len + 2 < end) { p = stpcpy(p, str); *(p++) = '\n'; }
|
||||
dbus_message_iter_next(&array_iter);
|
||||
}
|
||||
if (activated_handler) activated_handler(0, -1, buf);
|
||||
#undef check_call
|
||||
|
||||
}
|
||||
|
||||
static bool
|
||||
get_capabilities(DBusConnection *session_bus) {
|
||||
return glfw_dbus_call_method_with_reply(session_bus, NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE, "GetCapabilities", 60, got_capabilities, NULL, DBUS_TYPE_INVALID);
|
||||
}
|
||||
|
||||
notification_id_type
|
||||
glfw_dbus_send_user_notification(const GLFWDBUSNotificationData *n, GLFWDBusnotificationcreatedfun callback, void *user_data) {
|
||||
glfw_dbus_send_user_notification(const char *app_name, const char* icon, const char *summary, const char *body, const char* action_name, int32_t timeout, int urgency, GLFWDBusnotificationcreatedfun callback, void *user_data) {
|
||||
DBusConnection *session_bus = glfw_dbus_session_bus();
|
||||
if (!session_bus) return 0;
|
||||
if (n->timeout == -9999 && n->urgency == 255) return cancel_user_notification(session_bus, user_data) ? 1 : 0;
|
||||
if (n->timeout == -99999 && n->urgency == 255) return get_capabilities(session_bus) ? 1 : 0;
|
||||
static DBusConnection *added_signal_match = NULL;
|
||||
if (!session_bus) return 0;
|
||||
if (added_signal_match != session_bus) {
|
||||
dbus_bus_add_match(session_bus, "type='signal',interface='" NOTIFICATIONS_IFACE "',member='ActionInvoked'", NULL);
|
||||
dbus_bus_add_match(session_bus, "type='signal',interface='" NOTIFICATIONS_IFACE "',member='NotificationClosed'", NULL);
|
||||
dbus_bus_add_match(session_bus, "type='signal',interface='" NOTIFICATIONS_IFACE "',member='ActivationToken'", NULL);
|
||||
dbus_connection_add_filter(session_bus, message_handler, NULL, NULL);
|
||||
added_signal_match = session_bus;
|
||||
}
|
||||
RAII_ALLOC(NotificationCreatedData, data, malloc(sizeof(NotificationCreatedData)));
|
||||
if (!data) return 0;
|
||||
static notification_id_type notification_id = 0;
|
||||
data->next_id = ++notification_id;
|
||||
data->callback = callback; data->data = user_data;
|
||||
if (!data->next_id) data->next_id = ++notification_id;
|
||||
uint32_t replaces_id = 0;
|
||||
|
||||
RAII_MSG(msg, dbus_message_new_method_call(NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE, "Notify"));
|
||||
if (!msg) { return 0; }
|
||||
@@ -153,38 +86,33 @@ glfw_dbus_send_user_notification(const GLFWDBUSNotificationData *n, GLFWDBusnoti
|
||||
dbus_message_iter_init_append(msg, &args);
|
||||
#define check_call(func, ...) if (!func(__VA_ARGS__)) { _glfwInputError(GLFW_PLATFORM_ERROR, "%s", "Out of memory allocating DBUS message for notification\n"); return 0; }
|
||||
#define APPEND(to, type, val) check_call(dbus_message_iter_append_basic, &to, type, &val);
|
||||
APPEND(args, DBUS_TYPE_STRING, n->app_name)
|
||||
APPEND(args, DBUS_TYPE_UINT32, n->replaces)
|
||||
APPEND(args, DBUS_TYPE_STRING, n->icon)
|
||||
APPEND(args, DBUS_TYPE_STRING, n->summary)
|
||||
APPEND(args, DBUS_TYPE_STRING, n->body)
|
||||
APPEND(args, DBUS_TYPE_STRING, app_name)
|
||||
APPEND(args, DBUS_TYPE_UINT32, replaces_id)
|
||||
APPEND(args, DBUS_TYPE_STRING, icon)
|
||||
APPEND(args, DBUS_TYPE_STRING, summary)
|
||||
APPEND(args, DBUS_TYPE_STRING, body)
|
||||
check_call(dbus_message_iter_open_container, &args, DBUS_TYPE_ARRAY, "s", &array);
|
||||
if (n->actions) {
|
||||
for (size_t i = 0; i < n->num_actions; i++) {
|
||||
APPEND(array, DBUS_TYPE_STRING, n->actions[i]);
|
||||
}
|
||||
if (action_name) {
|
||||
static const char* default_action = "default";
|
||||
APPEND(array, DBUS_TYPE_STRING, default_action);
|
||||
APPEND(array, DBUS_TYPE_STRING, action_name);
|
||||
}
|
||||
check_call(dbus_message_iter_close_container, &args, &array);
|
||||
check_call(dbus_message_iter_open_container, &args, DBUS_TYPE_ARRAY, "{sv}", &array);
|
||||
|
||||
#define append_sv_dictionary_entry(k, val_type, val) { \
|
||||
check_call(dbus_message_iter_open_container, &array, DBUS_TYPE_DICT_ENTRY, NULL, &dict); \
|
||||
static const char *key = k; \
|
||||
APPEND(dict, DBUS_TYPE_STRING, key); \
|
||||
check_call(dbus_message_iter_open_container, &dict, DBUS_TYPE_VARIANT, val_type##_AS_STRING, &variant); \
|
||||
APPEND(variant, val_type, val); \
|
||||
check_call(dbus_message_iter_close_container, &dict, &variant); \
|
||||
check_call(dbus_message_iter_close_container, &array, &dict); \
|
||||
}
|
||||
append_sv_dictionary_entry("urgency", DBUS_TYPE_BYTE, n->urgency);
|
||||
if (n->category && n->category[0]) append_sv_dictionary_entry("category", DBUS_TYPE_STRING, n->category);
|
||||
if (n->muted) append_sv_dictionary_entry("suppress-sound", DBUS_TYPE_BOOLEAN, n->muted);
|
||||
|
||||
check_call(dbus_message_iter_open_container, &array, DBUS_TYPE_DICT_ENTRY, NULL, &dict);
|
||||
static const char* urgency_key = "urgency";
|
||||
APPEND(dict, DBUS_TYPE_STRING, urgency_key);
|
||||
check_call(dbus_message_iter_open_container, &dict, DBUS_TYPE_VARIANT, DBUS_TYPE_BYTE_AS_STRING, &variant);
|
||||
uint8_t urgencyb = urgency & 3;
|
||||
APPEND(variant, DBUS_TYPE_BYTE, urgencyb);
|
||||
check_call(dbus_message_iter_close_container, &dict, &variant);
|
||||
check_call(dbus_message_iter_close_container, &array, &dict);
|
||||
check_call(dbus_message_iter_close_container, &args, &array);
|
||||
APPEND(args, DBUS_TYPE_INT32, n->timeout)
|
||||
APPEND(args, DBUS_TYPE_INT32, 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;
|
||||
|
||||
4
glfw/linux_notify.h
vendored
4
glfw/linux_notify.h
vendored
@@ -12,8 +12,8 @@
|
||||
|
||||
typedef unsigned long long notification_id_type;
|
||||
typedef void (*GLFWDBusnotificationcreatedfun)(notification_id_type, uint32_t, void*);
|
||||
typedef void (*GLFWDBusnotificationactivatedfun)(uint32_t, int, const char*);
|
||||
typedef void (*GLFWDBusnotificationactivatedfun)(uint32_t, const char*);
|
||||
notification_id_type
|
||||
glfw_dbus_send_user_notification(const GLFWDBUSNotificationData *n, GLFWDBusnotificationcreatedfun, void*);
|
||||
glfw_dbus_send_user_notification(const char *app_name, const char* icon, const char *summary, const char *body, const char *action_name, int32_t timeout, int urgency, GLFWDBusnotificationcreatedfun, void*);
|
||||
void
|
||||
glfw_dbus_set_user_notification_activated_handler(GLFWDBusnotificationactivatedfun handler);
|
||||
|
||||
@@ -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",
|
||||
@@ -83,7 +82,6 @@
|
||||
"staging/cursor-shape/cursor-shape-v1.xml",
|
||||
"staging/fractional-scale/fractional-scale-v1.xml",
|
||||
"staging/single-pixel-buffer/single-pixel-buffer-v1.xml",
|
||||
"unstable/idle-inhibit/idle-inhibit-unstable-v1.xml",
|
||||
|
||||
"kwin-blur-v1.xml",
|
||||
"wlr-layer-shell-unstable-v1.xml"
|
||||
@@ -146,7 +144,6 @@
|
||||
"linux_joystick.h",
|
||||
"null_joystick.h",
|
||||
"linux_notify.h",
|
||||
"linux_desktop_settings.h",
|
||||
"main_loop.h"
|
||||
],
|
||||
"sources": [
|
||||
@@ -163,7 +160,6 @@
|
||||
"backend_utils.c",
|
||||
"linux_joystick.c",
|
||||
"null_joystick.c",
|
||||
"linux_desktop_settings.c",
|
||||
"linux_notify.c"
|
||||
]
|
||||
}
|
||||
|
||||
58
glfw/wl_client_side_decorations.c
vendored
58
glfw/wl_client_side_decorations.c
vendored
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -575,21 +577,21 @@ 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 needs_shadow = window_needs_shadows(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;
|
||||
const bool needs_update = focus_changed || size_changed || !decs.titlebar.surface || decs.buffer_destroyed || state_changed;
|
||||
debug("CSD: old.size: %dx%d new.size: %dx%d needs_update: %d size_changed: %d state_changed: %d buffer_destroyed: %d\n",
|
||||
decs.for_window_state.width, decs.for_window_state.height, window->wl.width, window->wl.height, needs_update,
|
||||
const bool shadow_changed = needs_shadow != decs.for_window_state.needs_shadow;
|
||||
debug("CSD: old.size: %dx%d new.size: %dx%d needs_update: %d shadow_changed: %d size_changed: %d state_changed: %d buffer_destroyed: %d\n",
|
||||
decs.for_window_state.width, decs.for_window_state.height, window->wl.width, window->wl.height, needs_update, shadow_changed,
|
||||
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) {
|
||||
if (size_changed || decs.buffer_destroyed || shadow_changed) {
|
||||
free_csd_buffers(window);
|
||||
if (!create_shm_buffers(window)) return false;
|
||||
decs.buffer_destroyed = false;
|
||||
@@ -600,14 +602,20 @@ ensure_csd_resources(_GLFWwindow *window) {
|
||||
position_csd_surface(&decs.which, x, y);
|
||||
|
||||
setup_surface(titlebar, 0, -decs.metrics.visible_titlebar_height);
|
||||
setup_surface(shadow_top, decs.titlebar.x, decs.titlebar.y - decs.metrics.width);
|
||||
setup_surface(shadow_bottom, decs.titlebar.x, window->wl.height);
|
||||
setup_surface(shadow_left, -decs.metrics.width, decs.titlebar.y);
|
||||
setup_surface(shadow_right, window->wl.width, decs.shadow_left.y);
|
||||
setup_surface(shadow_upper_left, decs.shadow_left.x, decs.shadow_top.y);
|
||||
setup_surface(shadow_upper_right, decs.shadow_right.x, decs.shadow_top.y);
|
||||
setup_surface(shadow_lower_left, decs.shadow_left.x, decs.shadow_bottom.y);
|
||||
setup_surface(shadow_lower_right, decs.shadow_right.x, decs.shadow_bottom.y);
|
||||
if (needs_shadow) {
|
||||
setup_surface(shadow_top, decs.titlebar.x, decs.titlebar.y - decs.metrics.width);
|
||||
setup_surface(shadow_bottom, decs.titlebar.x, window->wl.height);
|
||||
setup_surface(shadow_left, -decs.metrics.width, decs.titlebar.y);
|
||||
setup_surface(shadow_right, window->wl.width, decs.shadow_left.y);
|
||||
setup_surface(shadow_upper_left, decs.shadow_left.x, decs.shadow_top.y);
|
||||
setup_surface(shadow_upper_right, decs.shadow_right.x, decs.shadow_top.y);
|
||||
setup_surface(shadow_lower_left, decs.shadow_left.x, decs.shadow_bottom.y);
|
||||
setup_surface(shadow_lower_right, decs.shadow_right.x, decs.shadow_bottom.y);
|
||||
} else {
|
||||
#define d(which) free_csd_surface(&decs.which);
|
||||
all_shadow_surfaces(d)
|
||||
#undef d
|
||||
}
|
||||
|
||||
if (focus_changed || state_changed) update_title_bar(window);
|
||||
damage_csd(titlebar, decs.titlebar.buffer.front);
|
||||
@@ -618,8 +626,10 @@ 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;
|
||||
decs.for_window_state.needs_shadow = needs_shadow;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -702,7 +712,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) {
|
||||
@@ -759,7 +769,6 @@ handle_pointer_leave(_GLFWwindow *window, struct wl_surface *surface) {
|
||||
}
|
||||
#undef c
|
||||
decs.focus = CENTRAL_WINDOW;
|
||||
decs.dragging = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -769,11 +778,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;
|
||||
@@ -794,7 +799,6 @@ handle_pointer_enter(_GLFWwindow *window, struct wl_surface *surface) {
|
||||
all_surfaces(Q)
|
||||
#undef Q
|
||||
decs.focus = CENTRAL_WINDOW;
|
||||
decs.dragging = false;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -822,7 +826,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;
|
||||
|
||||
13
glfw/wl_init.c
vendored
13
glfw/wl_init.c
vendored
@@ -44,8 +44,6 @@
|
||||
#include <sys/socket.h>
|
||||
#include <wayland-client.h>
|
||||
#include <stdio.h>
|
||||
// errno.h needed for BSD code paths
|
||||
#include <errno.h>
|
||||
// Needed for the BTN_* defines
|
||||
#ifdef __has_include
|
||||
#if __has_include(<linux/input.h>)
|
||||
@@ -599,9 +597,6 @@ static void registryHandleGlobal(void* data UNUSED,
|
||||
_glfw.wl.zwlr_layer_shell_v1 = wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, version);
|
||||
}
|
||||
}
|
||||
else if (is(zwp_idle_inhibit_manager_v1)) {
|
||||
_glfw.wl.idle_inhibit_manager = wl_registry_bind(registry, name, &zwp_idle_inhibit_manager_v1_interface, 1);
|
||||
}
|
||||
#undef is
|
||||
}
|
||||
|
||||
@@ -636,13 +631,12 @@ static const struct wl_registry_listener registryListener = {
|
||||
};
|
||||
|
||||
|
||||
GLFWAPI GLFWColorScheme glfwGetCurrentSystemColorTheme(bool query_if_unintialized) {
|
||||
return glfw_current_system_color_theme(query_if_unintialized);
|
||||
GLFWAPI GLFWColorScheme glfwGetCurrentSystemColorTheme(void) {
|
||||
return glfw_current_system_color_theme();
|
||||
}
|
||||
|
||||
static pid_t
|
||||
get_socket_peer_pid(int fd) {
|
||||
(void)fd;
|
||||
#ifdef __linux__
|
||||
struct ucred ucred;
|
||||
socklen_t len = sizeof(struct ucred);
|
||||
@@ -682,7 +676,6 @@ get_compositor_missing_capabilities(void) {
|
||||
C(blur, org_kde_kwin_blur_manager); C(server_side_decorations, decorationManager);
|
||||
C(cursor_shape, wp_cursor_shape_manager_v1); C(layer_shell, zwlr_layer_shell_v1);
|
||||
C(single_pixel_buffer, wp_single_pixel_buffer_manager_v1); C(preferred_scale, has_preferred_buffer_scale);
|
||||
C(idle_inhibit, idle_inhibit_manager);
|
||||
#undef C
|
||||
return buf;
|
||||
}
|
||||
@@ -865,8 +858,6 @@ void _glfwPlatformTerminate(void)
|
||||
org_kde_kwin_blur_manager_destroy(_glfw.wl.org_kde_kwin_blur_manager);
|
||||
if (_glfw.wl.zwlr_layer_shell_v1)
|
||||
zwlr_layer_shell_v1_destroy(_glfw.wl.zwlr_layer_shell_v1);
|
||||
if (_glfw.wl.idle_inhibit_manager)
|
||||
zwp_idle_inhibit_manager_v1_destroy(_glfw.wl.idle_inhibit_manager);
|
||||
|
||||
if (_glfw.wl.registry)
|
||||
wl_registry_destroy(_glfw.wl.registry);
|
||||
|
||||
21
glfw/wl_monitor.c
vendored
21
glfw/wl_monitor.c
vendored
@@ -31,6 +31,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
|
||||
|
||||
@@ -89,6 +90,7 @@ static void outputHandleDone(void* data, struct wl_output* output UNUSED)
|
||||
for (int i = 0; i < _glfw.monitorCount; i++) {
|
||||
if (_glfw.monitors[i] == monitor) return;
|
||||
}
|
||||
|
||||
_glfwInputMonitor(monitor, GLFW_CONNECTED, _GLFW_INSERT_LAST);
|
||||
}
|
||||
|
||||
@@ -101,28 +103,11 @@ static void outputHandleScale(void* data,
|
||||
monitor->wl.scale = factor;
|
||||
}
|
||||
|
||||
static void outputHandleName(void* data,
|
||||
struct wl_output* output UNUSED,
|
||||
const char* name) {
|
||||
struct _GLFWmonitor *monitor = data;
|
||||
if (name) strncpy(monitor->wl.friendly_name, name, sizeof(monitor->wl.friendly_name)-1);
|
||||
}
|
||||
|
||||
static void outputHandleDescription(void* data,
|
||||
struct wl_output* output UNUSED,
|
||||
const char* description) {
|
||||
struct _GLFWmonitor *monitor = data;
|
||||
if (description) strncpy(monitor->wl.description, description, sizeof(monitor->wl.description)-1);
|
||||
|
||||
}
|
||||
|
||||
static const struct wl_output_listener outputListener = {
|
||||
outputHandleGeometry,
|
||||
outputHandleMode,
|
||||
outputHandleDone,
|
||||
outputHandleScale,
|
||||
outputHandleName,
|
||||
outputHandleDescription,
|
||||
};
|
||||
|
||||
|
||||
@@ -148,7 +133,7 @@ void _glfwAddOutputWayland(uint32_t name, uint32_t version)
|
||||
output = wl_registry_bind(_glfw.wl.registry,
|
||||
name,
|
||||
&wl_output_interface,
|
||||
MIN(version, (unsigned)WL_OUTPUT_NAME_SINCE_VERSION));
|
||||
2);
|
||||
if (!output)
|
||||
{
|
||||
_glfwFreeMonitor(monitor);
|
||||
|
||||
7
glfw/wl_platform.h
vendored
7
glfw/wl_platform.h
vendored
@@ -65,7 +65,6 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
|
||||
#include "wayland-kwin-blur-v1-client-protocol.h"
|
||||
#include "wayland-wlr-layer-shell-unstable-v1-client-protocol.h"
|
||||
#include "wayland-single-pixel-buffer-v1-client-protocol.h"
|
||||
#include "wayland-idle-inhibit-unstable-v1-client-protocol.h"
|
||||
|
||||
#define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL)
|
||||
#define _glfw_dlclose(handle) dlclose(handle)
|
||||
@@ -221,7 +220,7 @@ typedef struct _GLFWwindowWayland
|
||||
} pointerLock;
|
||||
|
||||
struct {
|
||||
bool serverSide, buffer_destroyed, titlebar_needs_update, dragging;
|
||||
bool serverSide, buffer_destroyed, titlebar_needs_update;
|
||||
_GLFWCSDSurface focus;
|
||||
|
||||
_GLFWWaylandCSDSurface titlebar, shadow_left, shadow_right, shadow_top, shadow_bottom, shadow_upper_left, shadow_upper_right, shadow_lower_left, shadow_lower_right;
|
||||
@@ -233,7 +232,7 @@ typedef struct _GLFWwindowWayland
|
||||
|
||||
struct {
|
||||
int width, height;
|
||||
bool focused;
|
||||
bool focused, needs_shadow;
|
||||
double fscale;
|
||||
WaylandWindowState toplevel_states;
|
||||
} for_window_state;
|
||||
@@ -339,7 +338,6 @@ typedef struct _GLFWlibraryWayland
|
||||
struct org_kde_kwin_blur_manager *org_kde_kwin_blur_manager;
|
||||
struct zwlr_layer_shell_v1* zwlr_layer_shell_v1; uint32_t zwlr_layer_shell_v1_version;
|
||||
struct wp_single_pixel_buffer_manager_v1 *wp_single_pixel_buffer_manager_v1;
|
||||
struct zwp_idle_inhibit_manager_v1* idle_inhibit_manager;
|
||||
|
||||
int compositorVersion;
|
||||
int seatVersion;
|
||||
@@ -397,7 +395,6 @@ typedef struct _GLFWmonitorWayland
|
||||
{
|
||||
struct wl_output* output;
|
||||
uint32_t name;
|
||||
char friendly_name[64], description[64];
|
||||
int currentMode;
|
||||
|
||||
int x;
|
||||
|
||||
113
glfw/wl_window.c
vendored
113
glfw/wl_window.c
vendored
@@ -251,7 +251,8 @@ setCursorImage(_GLFWwindow* window, bool on_theme_change) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: late cursor load failed; proceeding with existing cursor");
|
||||
}
|
||||
}
|
||||
if (!cursorWayland->cursor || !cursorWayland->cursor->image_count || !cursorWayland->cursor->images) return;
|
||||
if (!cursorWayland->cursor || !cursorWayland->cursor->image_count)
|
||||
return;
|
||||
if (cursorWayland->currentImage >= cursorWayland->cursor->image_count) cursorWayland->currentImage = 0;
|
||||
image = cursorWayland->cursor->images[cursorWayland->currentImage];
|
||||
if (!image) image = cursorWayland->cursor->images[0];
|
||||
@@ -370,8 +371,8 @@ wait_for_swap_to_commit(_GLFWwindow *window) {
|
||||
static void
|
||||
resizeFramebuffer(_GLFWwindow* window) {
|
||||
GLFWwindow *ctx = glfwGetCurrentContext();
|
||||
bool ctx_changed = false;
|
||||
if (ctx != (GLFWwindow*)window && window->context.client != GLFW_NO_API) { ctx_changed = true; glfwMakeContextCurrent((GLFWwindow*)window); }
|
||||
const bool ctx_changed = ctx != (GLFWwindow*)window;
|
||||
if (ctx_changed) glfwMakeContextCurrent((GLFWwindow*)window);
|
||||
double scale = _glfwWaylandWindowScale(window);
|
||||
int scaled_width = (int)round(window->wl.width * scale);
|
||||
int scaled_height = (int)round(window->wl.height * scale);
|
||||
@@ -823,7 +824,7 @@ create_single_color_buffer(int width, int height, pixel color) {
|
||||
if (width == 1 && height == 1 && _glfw.wl.wp_single_pixel_buffer_manager_v1) {
|
||||
#define C(x) (uint32_t)(((double)((uint64_t)color.alpha * color.x * UINT32_MAX)) / (255 * 255))
|
||||
struct wl_buffer *ans = wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer(
|
||||
_glfw.wl.wp_single_pixel_buffer_manager_v1, C(red), C(green), C(blue), (uint32_t)((color.alpha / 255.) * UINT32_MAX));
|
||||
_glfw.wl.wp_single_pixel_buffer_manager_v1, C(red), C(green), C(blue), color.alpha * UINT32_MAX);
|
||||
#undef C
|
||||
if (!ans) _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: failed to create single pixel buffer");
|
||||
return ans;
|
||||
@@ -952,10 +953,14 @@ void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, bool enabled UNUSED) {
|
||||
|
||||
static struct wl_output*
|
||||
find_output_by_name(const char* name) {
|
||||
if (!name || !name[0]) return NULL;
|
||||
for (int i = 0; i < _glfw.monitorCount; i++) {
|
||||
_GLFWmonitor *m = _glfw.monitors[i];
|
||||
if (strcmp(m->wl.friendly_name, name) == 0) return m->wl.output;
|
||||
if (!name) return NULL;
|
||||
int count;
|
||||
GLFWmonitor** monitors = glfwGetMonitors(&count);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
_GLFWmonitor *m = (_GLFWmonitor*)monitors + i;
|
||||
if (strcmp(m->name, name) == 0) {
|
||||
return m->wl.output;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -963,7 +968,7 @@ find_output_by_name(const char* name) {
|
||||
static void
|
||||
layer_set_properties(_GLFWwindow *window) {
|
||||
enum zwlr_layer_surface_v1_anchor which_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
|
||||
int exclusive_zone = window->wl.layer_shell.config.requested_exclusive_zone;
|
||||
int exclusive_zone = -1;
|
||||
enum zwlr_layer_surface_v1_keyboard_interactivity focus_policy = ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE;
|
||||
switch(window->wl.layer_shell.config.focus_policy) {
|
||||
case GLFW_FOCUS_NOT_ALLOWED: focus_policy = ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE; break;
|
||||
@@ -972,36 +977,28 @@ layer_set_properties(_GLFWwindow *window) {
|
||||
}
|
||||
int panel_width = 0, panel_height = 0;
|
||||
switch (window->wl.layer_shell.config.type) {
|
||||
case GLFW_LAYER_SHELL_NONE: break;
|
||||
case GLFW_LAYER_SHELL_BACKGROUND: exclusive_zone = -1; break;
|
||||
case GLFW_LAYER_SHELL_TOP:
|
||||
case GLFW_LAYER_SHELL_OVERLAY:
|
||||
case GLFW_LAYER_SHELL_BACKGROUND: break; case GLFW_LAYER_SHELL_NONE: break;
|
||||
case GLFW_LAYER_SHELL_PANEL:
|
||||
switch (window->wl.layer_shell.config.edge) {
|
||||
case GLFW_EDGE_TOP:
|
||||
which_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
|
||||
panel_height = window->wl.height;
|
||||
if (!window->wl.layer_shell.config.override_exclusive_zone) exclusive_zone = window->wl.height;
|
||||
exclusive_zone = window->wl.height;
|
||||
break;
|
||||
case GLFW_EDGE_BOTTOM:
|
||||
which_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
|
||||
panel_height = window->wl.height;
|
||||
if (!window->wl.layer_shell.config.override_exclusive_zone) exclusive_zone = window->wl.height;
|
||||
exclusive_zone = window->wl.height;
|
||||
break;
|
||||
case GLFW_EDGE_LEFT:
|
||||
which_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
|
||||
panel_width = window->wl.width;
|
||||
if (!window->wl.layer_shell.config.override_exclusive_zone) exclusive_zone = window->wl.width;
|
||||
exclusive_zone = window->wl.width;
|
||||
break;
|
||||
case GLFW_EDGE_RIGHT:
|
||||
which_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
|
||||
panel_width = window->wl.width;
|
||||
if (!window->wl.layer_shell.config.override_exclusive_zone) exclusive_zone = window->wl.width;
|
||||
break;
|
||||
case GLFW_EDGE_NONE:
|
||||
which_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; // None is anchored to "top left"
|
||||
panel_width = window->wl.width;
|
||||
panel_height = window->wl.height;
|
||||
exclusive_zone = window->wl.width;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1011,7 +1008,7 @@ layer_set_properties(_GLFWwindow *window) {
|
||||
debug("Compositor will be informed that layer size: %dx%d viewport: %dx%d at next surface commit\n", panel_width, panel_height, window->wl.width, window->wl.height);
|
||||
zwlr_layer_surface_v1_set_anchor(surface, which_anchor);
|
||||
zwlr_layer_surface_v1_set_exclusive_zone(surface, exclusive_zone);
|
||||
zwlr_layer_surface_v1_set_margin(surface, window->wl.layer_shell.config.requested_top_margin, window->wl.layer_shell.config.requested_right_margin, window->wl.layer_shell.config.requested_bottom_margin, window->wl.layer_shell.config.requested_left_margin);
|
||||
zwlr_layer_surface_v1_set_margin(surface, 0, 0, 0, 0);
|
||||
zwlr_layer_surface_v1_set_keyboard_interactivity(surface, focus_policy);
|
||||
#undef surface
|
||||
}
|
||||
@@ -1064,13 +1061,8 @@ create_layer_shell_surface(_GLFWwindow *window) {
|
||||
}
|
||||
window->decorated = false; // shell windows must not have decorations
|
||||
struct wl_output *wl_output = find_output_by_name(window->wl.layer_shell.config.output_name);
|
||||
enum zwlr_layer_shell_v1_layer which_layer = ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND; // Default to background
|
||||
switch (window->wl.layer_shell.config.type) {
|
||||
case GLFW_LAYER_SHELL_BACKGROUND: break; case GLFW_LAYER_SHELL_NONE: break;
|
||||
case GLFW_LAYER_SHELL_PANEL: which_layer = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; break;
|
||||
case GLFW_LAYER_SHELL_TOP: which_layer = ZWLR_LAYER_SHELL_V1_LAYER_TOP; break;
|
||||
case GLFW_LAYER_SHELL_OVERLAY: which_layer = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; break;
|
||||
}
|
||||
enum zwlr_layer_shell_v1_layer which_layer = ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND;
|
||||
if (window->wl.layer_shell.config.type == GLFW_LAYER_SHELL_PANEL) which_layer = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
|
||||
#define ls window->wl.layer_shell.zwlr_layer_surface_v1
|
||||
ls = zwlr_layer_shell_v1_get_layer_surface(
|
||||
_glfw.wl.zwlr_layer_shell_v1, window->wl.surface, wl_output, which_layer, "kitty");
|
||||
@@ -1332,8 +1324,30 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
||||
window->swaps_disallowed = true;
|
||||
|
||||
if (!createSurface(window, wndconfig)) return false;
|
||||
if (wndconfig->title) window->wl.title = _glfw_strdup(wndconfig->title);
|
||||
if (wndconfig->maximized) window->wl.maximize_on_first_show = true;
|
||||
|
||||
if (ctxconfig->client != GLFW_NO_API)
|
||||
{
|
||||
if (ctxconfig->source == GLFW_EGL_CONTEXT_API ||
|
||||
ctxconfig->source == GLFW_NATIVE_CONTEXT_API)
|
||||
{
|
||||
if (!_glfwInitEGL())
|
||||
return false;
|
||||
if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig))
|
||||
return false;
|
||||
}
|
||||
else if (ctxconfig->source == GLFW_OSMESA_CONTEXT_API)
|
||||
{
|
||||
if (!_glfwInitOSMesa())
|
||||
return false;
|
||||
if (!_glfwCreateContextOSMesa(window, ctxconfig, fbconfig))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (wndconfig->title)
|
||||
window->wl.title = _glfw_strdup(wndconfig->title);
|
||||
if (wndconfig->maximized)
|
||||
window->wl.maximize_on_first_show = true;
|
||||
|
||||
if (wndconfig->visible)
|
||||
{
|
||||
@@ -1356,29 +1370,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
||||
window->wl.monitors = calloc(1, sizeof(_GLFWmonitor*));
|
||||
window->wl.monitorsCount = 0;
|
||||
window->wl.monitorsSize = 1;
|
||||
// looping till window fully created attaches a single pixel buffer to the window,
|
||||
// this cannot be done once a OpenGL context is created for the window. So first loop
|
||||
// and only then create the OpenGL context.
|
||||
if (window->wl.visible) loop_till_window_fully_created(window);
|
||||
debug("Creating OpenGL context and attaching it to window\n");
|
||||
if (ctxconfig->client != GLFW_NO_API)
|
||||
{
|
||||
if (ctxconfig->source == GLFW_EGL_CONTEXT_API ||
|
||||
ctxconfig->source == GLFW_NATIVE_CONTEXT_API)
|
||||
{
|
||||
if (!_glfwInitEGL())
|
||||
return false;
|
||||
if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig))
|
||||
return false;
|
||||
}
|
||||
else if (ctxconfig->source == GLFW_OSMESA_CONTEXT_API)
|
||||
{
|
||||
if (!_glfwInitOSMesa())
|
||||
return false;
|
||||
if (!_glfwCreateContextOSMesa(window, ctxconfig, fbconfig))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1407,6 +1399,7 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
|
||||
wp_viewport_destroy(window->wl.wp_viewport);
|
||||
if (window->wl.org_kde_kwin_blur)
|
||||
org_kde_kwin_blur_release(window->wl.org_kde_kwin_blur);
|
||||
if (window->wl.layer_shell.config.output_name) free((void*)window->wl.layer_shell.config.output_name);
|
||||
|
||||
if (window->context.destroy)
|
||||
window->context.destroy(window);
|
||||
@@ -1610,6 +1603,7 @@ void _glfwPlatformShowWindow(_GLFWwindow* window)
|
||||
if (!window->wl.visible) {
|
||||
create_window_desktop_surface(window);
|
||||
window->wl.visible = true;
|
||||
loop_till_window_fully_created(window);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2731,8 +2725,8 @@ GLFWAPI void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long
|
||||
}
|
||||
}
|
||||
|
||||
GLFWAPI unsigned long long glfwDBusUserNotify(const GLFWDBUSNotificationData *n, GLFWDBusnotificationcreatedfun callback, void *data) {
|
||||
return glfw_dbus_send_user_notification(n, callback, data);
|
||||
GLFWAPI unsigned long long glfwDBusUserNotify(const char *app_name, const char* icon, const char *summary, const char *body, const char *action_name, int32_t timeout, int urgency, GLFWDBusnotificationcreatedfun callback, void *data) {
|
||||
return glfw_dbus_send_user_notification(app_name, icon, summary, body, action_name, timeout, urgency, callback, data);
|
||||
}
|
||||
|
||||
GLFWAPI void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler) {
|
||||
@@ -2753,8 +2747,11 @@ GLFWAPI void glfwWaylandRedrawCSDWindowTitle(GLFWwindow *handle) {
|
||||
if (csd_change_title(window)) commit_window_surface_if_safe(window);
|
||||
}
|
||||
|
||||
GLFWAPI void glfwWaylandSetupLayerShellForNextWindow(const GLFWLayerShellConfig *c) {
|
||||
layer_shell_config_for_next_window = *c;
|
||||
GLFWAPI void glfwWaylandSetupLayerShellForNextWindow(GLFWLayerShellConfig c) {
|
||||
if (layer_shell_config_for_next_window.output_name) free((void*)layer_shell_config_for_next_window.output_name);
|
||||
layer_shell_config_for_next_window = c;
|
||||
if (layer_shell_config_for_next_window.output_name && !layer_shell_config_for_next_window.output_name[0]) layer_shell_config_for_next_window.output_name = NULL;
|
||||
if (layer_shell_config_for_next_window.output_name) layer_shell_config_for_next_window.output_name = strdup(layer_shell_config_for_next_window.output_name);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
6
glfw/x11_init.c
vendored
6
glfw/x11_init.c
vendored
@@ -30,7 +30,6 @@
|
||||
#define _GNU_SOURCE
|
||||
#include "internal.h"
|
||||
#include "backend_utils.h"
|
||||
#include "linux_desktop_settings.h"
|
||||
|
||||
#include <X11/Xresource.h>
|
||||
|
||||
@@ -615,8 +614,8 @@ Cursor _glfwCreateCursorX11(const GLFWimage* image, int xhot, int yhot)
|
||||
////// GLFW platform API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
GLFWAPI GLFWColorScheme glfwGetCurrentSystemColorTheme(bool query_if_unintialized) {
|
||||
return glfw_current_system_color_theme(query_if_unintialized);
|
||||
GLFWAPI GLFWColorScheme glfwGetCurrentSystemColorTheme(void) {
|
||||
return GLFW_COLOR_SCHEME_NO_PREFERENCE;
|
||||
}
|
||||
|
||||
void _glfwPlatformInputColorScheme(GLFWColorScheme appearance UNUSED) { }
|
||||
@@ -649,7 +648,6 @@ int _glfwPlatformInit(void)
|
||||
"X11: Failed to initialize event loop data");
|
||||
}
|
||||
glfw_dbus_init(&_glfw.x11.dbus, &_glfw.x11.eventLoopData);
|
||||
glfw_initialize_desktop_settings(); // needed for color scheme change notification
|
||||
|
||||
_glfw.x11.screen = DefaultScreen(_glfw.x11.display);
|
||||
_glfw.x11.root = RootWindow(_glfw.x11.display, _glfw.x11.screen);
|
||||
|
||||
4
glfw/x11_window.c
vendored
4
glfw/x11_window.c
vendored
@@ -3253,8 +3253,8 @@ GLFWAPI int glfwGetNativeKeyForName(const char* keyName, bool caseSensitive) {
|
||||
return glfw_xkb_keysym_from_name(keyName, caseSensitive);
|
||||
}
|
||||
|
||||
GLFWAPI unsigned long long glfwDBusUserNotify(const GLFWDBUSNotificationData *n, GLFWDBusnotificationcreatedfun callback, void *data) {
|
||||
return glfw_dbus_send_user_notification(n, callback, data);
|
||||
GLFWAPI unsigned long long glfwDBusUserNotify(const char *app_name, const char* icon, const char *summary, const char *body, const char *action_name, int32_t timeout, int urgency,GLFWDBusnotificationcreatedfun callback, void *data) {
|
||||
return glfw_dbus_send_user_notification(app_name, icon, summary, body, action_name, timeout, urgency, callback, data);
|
||||
}
|
||||
|
||||
GLFWAPI void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler) {
|
||||
|
||||
12
go.mod
12
go.mod
@@ -1,22 +1,22 @@
|
||||
module kitty
|
||||
|
||||
go 1.23
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/ALTree/bigfloat v0.2.0
|
||||
github.com/alecthomas/chroma/v2 v2.14.0
|
||||
github.com/bmatcuk/doublestar/v4 v4.7.1
|
||||
github.com/dlclark/regexp2 v1.11.4
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1
|
||||
github.com/dlclark/regexp2 v1.11.0
|
||||
github.com/edwvee/exiffix v0.0.0-20240229113213-0dbb146775be
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/kovidgoyal/imaging v1.6.3
|
||||
github.com/seancfoley/ipaddress-go v1.7.0
|
||||
github.com/seancfoley/ipaddress-go v1.6.0
|
||||
github.com/shirou/gopsutil/v3 v3.24.5
|
||||
github.com/zeebo/xxh3 v1.0.2
|
||||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b
|
||||
golang.org/x/image v0.23.0
|
||||
golang.org/x/sys v0.28.0
|
||||
golang.org/x/image v0.17.0
|
||||
golang.org/x/sys v0.21.0
|
||||
howett.net/plist v1.0.1
|
||||
)
|
||||
|
||||
|
||||
20
go.sum
20
go.sum
@@ -6,14 +6,14 @@ github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46
|
||||
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
|
||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/bmatcuk/doublestar/v4 v4.7.1 h1:fdDeAqgT47acgwd9bd9HxJRDmc9UAmPpc+2m0CXv75Q=
|
||||
github.com/bmatcuk/doublestar/v4 v4.7.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
|
||||
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/edwvee/exiffix v0.0.0-20240229113213-0dbb146775be h1:FNPYI8/ifKGW7kdBdlogyGGaPXZmOXBbV1uz4Amr3s0=
|
||||
github.com/edwvee/exiffix v0.0.0-20240229113213-0dbb146775be/go.mod h1:G3dK5MziX9e4jUa8PWjowCOPCcyQwxsZ5a0oYA73280=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
@@ -40,8 +40,8 @@ github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||
github.com/seancfoley/bintree v1.3.1 h1:cqmmQK7Jm4aw8gna0bP+huu5leVOgHGSJBEpUx3EXGI=
|
||||
github.com/seancfoley/bintree v1.3.1/go.mod h1:hIUabL8OFYyFVTQ6azeajbopogQc2l5C/hiXMcemWNU=
|
||||
github.com/seancfoley/ipaddress-go v1.7.0 h1:vWp3SR3k+HkV3aKiNO2vEe6xbVxS0x/Ixw6hgyP238s=
|
||||
github.com/seancfoley/ipaddress-go v1.7.0/go.mod h1:TQRZgv+9jdvzHmKoPGBMxyiaVmoI0rYpfEk8Q/sL/Iw=
|
||||
github.com/seancfoley/ipaddress-go v1.6.0 h1:9z7yGmOnV4P2ML/dlR/kCJiv5tp8iHOOetJvxJh/R5w=
|
||||
github.com/seancfoley/ipaddress-go v1.6.0/go.mod h1:TQRZgv+9jdvzHmKoPGBMxyiaVmoI0rYpfEk8Q/sL/Iw=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
@@ -63,15 +63,15 @@ github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaD
|
||||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI=
|
||||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
|
||||
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
||||
golang.org/x/image v0.17.0 h1:nTRVVdajgB8zCMZVsViyzhnMKPwYeroEERRC64JuLco=
|
||||
golang.org/x/image v0.17.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
|
||||
|
||||
@@ -150,7 +150,7 @@ func GetChoices(o *Options) (response string, err error) {
|
||||
|
||||
ctx := style.Context{AllowEscapeCodes: true}
|
||||
|
||||
draw_choice_boxes := func(y, screen_width, _ int, choices ...Choice) {
|
||||
draw_choice_boxes := func(y, screen_width, screen_height int, choices ...Choice) {
|
||||
clickable_ranges = map[string][]Range{}
|
||||
width := screen_width - 2
|
||||
current_line_length := 0
|
||||
@@ -402,7 +402,7 @@ func GetChoices(o *Options) (response string, err error) {
|
||||
if ev.MatchesPressOrRepeat("esc") || ev.MatchesPressOrRepeat("ctrl+c") {
|
||||
ev.Handled = true
|
||||
lp.Quit(1)
|
||||
} else if ev.MatchesPressOrRepeat("enter") || ev.MatchesPressOrRepeat("kp_enter") {
|
||||
} else if ev.MatchesPressOrRepeat("enter") {
|
||||
ev.Handled = true
|
||||
response = response_on_accept
|
||||
lp.Quit(0)
|
||||
|
||||
@@ -65,9 +65,6 @@ func (k *kitty_font_backend_type) start() (err error) {
|
||||
var kitty_font_backend kitty_font_backend_type
|
||||
|
||||
func (k *kitty_font_backend_type) send(v any) error {
|
||||
if k.to == nil {
|
||||
return fmt.Errorf("Trying to send data when to pipe is nil")
|
||||
}
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not encode message to kitty with error: %w", err)
|
||||
|
||||
@@ -48,12 +48,10 @@ def setup_debug_print() -> bool:
|
||||
|
||||
|
||||
def send_to_kitten(x: Any) -> None:
|
||||
f = sys.__stdout__
|
||||
assert f is not None
|
||||
try:
|
||||
f.buffer.write(json.dumps(x).encode())
|
||||
f.buffer.write(b'\n')
|
||||
f.buffer.flush()
|
||||
sys.__stdout__.buffer.write(json.dumps(x).encode())
|
||||
sys.__stdout__.buffer.write(b'\n')
|
||||
sys.__stdout__.buffer.flush()
|
||||
except BrokenPipeError:
|
||||
raise SystemExit('Pipe to kitten was broken while sending data to it')
|
||||
|
||||
@@ -124,7 +122,7 @@ def get_features(features: Dict[str, Optional['FeatureData']]) -> Dict[str, FD]:
|
||||
return ans
|
||||
|
||||
|
||||
def render_face_sample(font: Descriptor, opts: Options, dpi_x: float, dpi_y: float, width: int, height: int, sample_text: str = '') -> RenderedSample:
|
||||
def render_face_sample(font: Descriptor, opts: Options, dpi_x: float, dpi_y: float, width: int, height: int) -> RenderedSample:
|
||||
face = face_from_descriptor(font, opts.font_size, dpi_x, dpi_y)
|
||||
face.set_size(opts.font_size, dpi_x, dpi_y)
|
||||
metadata = {
|
||||
@@ -141,7 +139,7 @@ def render_face_sample(font: Descriptor, opts: Options, dpi_x: float, dpi_y: flo
|
||||
if ns:
|
||||
metadata['variable_named_style'] = ns
|
||||
metadata['variable_axis_map'] = get_axis_map(face)
|
||||
bitmap, cell_width, cell_height = face.render_sample_text(sample_text or SAMPLE_TEXT, width, height, opts.foreground.rgb)
|
||||
bitmap, cell_width, cell_height = face.render_sample_text(SAMPLE_TEXT, width, height, opts.foreground.rgb)
|
||||
metadata['cell_width'] = cell_width
|
||||
metadata['cell_height'] = cell_height
|
||||
metadata['canvas_height'] = len(bitmap) // (4 *width)
|
||||
@@ -232,7 +230,7 @@ def query_kitty() -> Dict[str, str]:
|
||||
return ans
|
||||
|
||||
|
||||
def showcase(family: str = 'family="Fira Code"', sample_text: str = '') -> None:
|
||||
def showcase(family: str = 'family="Fira Code"') -> None:
|
||||
q = query_kitty()
|
||||
opts = Options()
|
||||
opts.foreground = to_color(q['foreground'])
|
||||
@@ -244,7 +242,7 @@ def showcase(family: str = 'family="Fira Code"', sample_text: str = '') -> None:
|
||||
ss = screen_size_function()()
|
||||
width = ss.cell_width * ss.cols
|
||||
height = 5 * ss.cell_height
|
||||
bitmap, m = render_face_sample(desc, opts, float(q['dpi_x']), float(q['dpi_y']), width, height, sample_text=sample_text)
|
||||
bitmap, m = render_face_sample(desc, opts, float(q['dpi_x']), float(q['dpi_y']), width, height)
|
||||
display_bitmap(bitmap, m['canvas_width'], m['canvas_height'])
|
||||
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ func (self *final_pane) draw_screen() (err error) {
|
||||
"",
|
||||
"What would you like to do?",
|
||||
"",
|
||||
fmt.Sprintf("%s to modify %s and use the new fonts", h("Enter"), s("italic", self.handler.opts.Config_file_name)),
|
||||
fmt.Sprintf("%s to modify %s and use the new fonts", h("Enter"), s("italic", `kitty.conf`)),
|
||||
"",
|
||||
fmt.Sprintf("%s to abort and return to font selection", h("Esc")),
|
||||
"",
|
||||
@@ -78,12 +78,7 @@ func (self *final_pane) on_key_event(event *loop.KeyEvent) (err error) {
|
||||
if event.MatchesPressOrRepeat("enter") {
|
||||
event.Handled = true
|
||||
patcher := config.Patcher{Write_backup: true}
|
||||
path := ""
|
||||
if filepath.IsAbs(self.handler.opts.Config_file_name) {
|
||||
path = self.handler.opts.Config_file_name
|
||||
} else {
|
||||
path = filepath.Join(utils.ConfigDir(), self.handler.opts.Config_file_name)
|
||||
}
|
||||
path := filepath.Join(utils.ConfigDir(), "kitty.conf")
|
||||
updated, err := patcher.Patch(path, "KITTY_FONTS", self.settings.serialized(), "font_family", "bold_font", "italic_font", "bold_italic_font")
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -68,8 +68,7 @@ func main(opts *Options) (rc int, err error) {
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Reload_in string
|
||||
Config_file_name string
|
||||
Reload_in string
|
||||
}
|
||||
|
||||
func EntryPoint(root *cli.Command) {
|
||||
@@ -94,19 +93,7 @@ func EntryPoint(root *cli.Command) {
|
||||
running in to reload its config, after making changes. Use this option to
|
||||
instead either not reload the config at all or in all running kitty instances.`,
|
||||
})
|
||||
ans.Add(cli.OptionSpec{
|
||||
Name: "--config-file-name",
|
||||
Dest: "Config_file_name",
|
||||
Type: "str",
|
||||
Default: "kitty.conf",
|
||||
Help: `The name or path to the config file to edit. Relative paths are interpreted
|
||||
with respect to the kitty config directory. By default the kitty config
|
||||
file, kitty.conf is edited. This is most useful if you add include
|
||||
fonts.conf to your kitty.conf and then have the kitten operate only on
|
||||
fonts.conf, allowing kitty.conf to remain unchanged.`,
|
||||
})
|
||||
|
||||
clone := root.AddClone(ans.Group, ans)
|
||||
clone.Hidden = true
|
||||
clone.Hidden = false
|
||||
clone.Name = "choose_fonts"
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
if __name__ == '__main__':
|
||||
import os
|
||||
import sys
|
||||
|
||||
from kitty.constants import kitten_exe
|
||||
os.execlp(kitten_exe(), 'kitten', *sys.argv)
|
||||
|
||||
@@ -110,7 +110,7 @@ func run_plain_text_loop(opts *Options) (err error) {
|
||||
defer tempfile.Close()
|
||||
}
|
||||
}
|
||||
lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoMouseTracking, loop.NoInBandResizeNotifications)
|
||||
lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoMouseTracking)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -139,24 +139,19 @@ func run_plain_text_loop(opts *Options) (err error) {
|
||||
|
||||
buf := make([]byte, 8192)
|
||||
write_one_chunk := func() error {
|
||||
orig := enc_writer.last_written_id
|
||||
for enc_writer.last_written_id == orig {
|
||||
n, err := data_src.Read(buf[:cap(buf)])
|
||||
if n > 0 {
|
||||
enc.Write(buf[:n])
|
||||
}
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
if errors.Is(err, io.EOF) {
|
||||
enc.Close()
|
||||
send_to_loop("\x1b\\")
|
||||
after_read_from_stdin()
|
||||
return nil
|
||||
}
|
||||
n, err := data_src.Read(buf[:cap(buf)])
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
send_to_loop("\x1b\\")
|
||||
return err
|
||||
}
|
||||
if n > 0 {
|
||||
enc.Write(buf[:n])
|
||||
}
|
||||
if errors.Is(err, io.EOF) {
|
||||
enc.Close()
|
||||
send_to_loop("\x1b\\")
|
||||
after_read_from_stdin()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -203,11 +203,7 @@ func unescape_metadata_value(k, x string) (ans string) {
|
||||
|
||||
func encode_bytes(metadata map[string]string, payload []byte) string {
|
||||
ans := strings.Builder{}
|
||||
enc_payload := ""
|
||||
if len(payload) > 0 {
|
||||
enc_payload = base64.StdEncoding.EncodeToString(payload)
|
||||
}
|
||||
ans.Grow(2048 + len(enc_payload))
|
||||
ans.Grow(2048)
|
||||
ans.WriteString("\x1b]")
|
||||
ans.WriteString(OSC_NUMBER)
|
||||
ans.WriteString(";")
|
||||
@@ -221,7 +217,7 @@ func encode_bytes(metadata map[string]string, payload []byte) string {
|
||||
}
|
||||
if len(payload) > 0 {
|
||||
ans.WriteString(";")
|
||||
ans.WriteString(enc_payload)
|
||||
ans.WriteString(base64.StdEncoding.EncodeToString(payload))
|
||||
}
|
||||
ans.WriteString("\x1b\\")
|
||||
return ans.String()
|
||||
@@ -286,7 +282,7 @@ func parse_aliases(raw []string) (map[string][]string, error) {
|
||||
}
|
||||
|
||||
func run_get_loop(opts *Options, args []string) (err error) {
|
||||
lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoMouseTracking, loop.NoInBandResizeNotifications)
|
||||
lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoMouseTracking)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func (self *Input) has_mime_matching(predicate func(string) bool) bool {
|
||||
}
|
||||
|
||||
func write_loop(inputs []*Input, opts *Options) (err error) {
|
||||
lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoMouseTracking, loop.NoInBandResizeNotifications)
|
||||
lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoMouseTracking)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -222,6 +222,8 @@ func run_set_loop(opts *Options, args []string) (err error) {
|
||||
return fmt.Errorf("Could not guess MIME type for %s use the --mime option to specify a MIME type", arg)
|
||||
}
|
||||
to_process[i] = inputs[i]
|
||||
if to_process[i].is_stream {
|
||||
}
|
||||
}
|
||||
return write_loop(to_process, opts)
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ func get_ssh_file(hostname, rpath string) (string, error) {
|
||||
for strings.HasPrefix(rpath, "/") {
|
||||
rpath = rpath[1:]
|
||||
}
|
||||
cmd := []string{ssh.SSHExe(), hostname, "tar", "--dereference", "--create", "--file", "-"}
|
||||
cmd := []string{ssh.SSHExe(), hostname, "tar", "-c", "-f", "-"}
|
||||
if is_abs {
|
||||
cmd = append(cmd, "-C", "/")
|
||||
}
|
||||
|
||||
@@ -221,14 +221,6 @@ map('Scroll to previous change',
|
||||
'prev_change p scroll_to prev-change',
|
||||
)
|
||||
|
||||
map('Scroll to next file',
|
||||
'next_file shift+j scroll_to next-file',
|
||||
)
|
||||
|
||||
map('Scroll to previous file',
|
||||
'prev_file shift+k scroll_to prev-file',
|
||||
)
|
||||
|
||||
map('Show all context',
|
||||
'all_context a change_context all',
|
||||
)
|
||||
|
||||
@@ -69,7 +69,6 @@ func (self *line_pos) Equal(other tui.LinePos) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *line_pos) LessThan(other tui.LinePos) bool {
|
||||
if o, ok := other.(*line_pos); ok {
|
||||
return self.y.Less(o.y)
|
||||
@@ -114,6 +113,7 @@ func (self *Handler) drag_scroll_tick(timer_id loop.IdType) error {
|
||||
}
|
||||
|
||||
var debugprintln = tty.DebugPrintln
|
||||
var _ = debugprintln
|
||||
|
||||
func (self *Handler) update_mouse_selection(ev *loop.MouseEvent) {
|
||||
if !self.mouse_selection.IsActive() {
|
||||
@@ -146,12 +146,6 @@ func (self *Handler) text_for_current_mouse_selection() string {
|
||||
}
|
||||
text := make([]byte, 0, 2048)
|
||||
start_pos, end_pos := *self.mouse_selection.StartLine().(*line_pos), *self.mouse_selection.EndLine().(*line_pos)
|
||||
|
||||
// if start is after end, swap them
|
||||
if end_pos.y.Less(start_pos.y) {
|
||||
start_pos, end_pos = end_pos, start_pos
|
||||
}
|
||||
|
||||
start, end := start_pos.y, end_pos.y
|
||||
is_left := start_pos.min_x == self.logical_lines.margin_size
|
||||
|
||||
|
||||
@@ -184,6 +184,7 @@ func (self *Handler) highlight_all() {
|
||||
self.async_results <- r
|
||||
self.lp.WakeupMainThread()
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
func (self *Handler) load_all_images() {
|
||||
@@ -522,27 +523,6 @@ func (self *Handler) scroll_to_next_change(backwards bool) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *Handler) scroll_to_next_file(backwards bool) bool {
|
||||
if backwards {
|
||||
for i := self.scroll_pos.logical_line - 1; i >= 0; i-- {
|
||||
line := self.logical_lines.At(i)
|
||||
if line.line_type == TITLE_LINE {
|
||||
self.scroll_pos = ScrollPos{i, 0}
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i := self.scroll_pos.logical_line + 1; i < self.logical_lines.Len(); i++ {
|
||||
line := self.logical_lines.At(i)
|
||||
if line.line_type == TITLE_LINE {
|
||||
self.scroll_pos = ScrollPos{i, 0}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *Handler) scroll_to_next_match(backwards, include_current_match bool) bool {
|
||||
if self.current_search == nil {
|
||||
return false
|
||||
@@ -636,8 +616,6 @@ func (self *Handler) dispatch_action(name, args string) error {
|
||||
case `scroll_to`:
|
||||
done := false
|
||||
switch {
|
||||
case strings.Contains(args, "file"):
|
||||
done = self.scroll_to_next_file(strings.Contains(args, `prev`))
|
||||
case strings.Contains(args, `change`):
|
||||
done = self.scroll_to_next_change(strings.Contains(args, `prev`))
|
||||
case strings.Contains(args, `match`):
|
||||
|
||||
@@ -217,11 +217,11 @@ func main(_ *cli.Command, o *Options, args []string) (rc int, err error) {
|
||||
}
|
||||
|
||||
lp.OnInitialize = func() (string, error) {
|
||||
lp.SendOverlayReady()
|
||||
lp.SetCursorVisible(false)
|
||||
lp.SetWindowTitle(window_title)
|
||||
lp.AllowLineWrapping(false)
|
||||
draw_screen()
|
||||
lp.SendOverlayReady()
|
||||
return "", nil
|
||||
}
|
||||
lp.OnFinalize = func() string {
|
||||
|
||||
@@ -427,28 +427,6 @@ func specialize_command(hg *cli.Command) {
|
||||
hg.ArgCompleter = cli.CompletionForWrapper("rg")
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
}
|
||||
|
||||
func create_cmd(root *cli.Command, run_func func(*cli.Command, *Options, []string) (int, error)) {
|
||||
ans := root.AddSubCommand(&cli.Command{
|
||||
Name: "hyperlinked_grep",
|
||||
Run: func(cmd *cli.Command, args []string) (int, error) {
|
||||
opts := Options{}
|
||||
err := cmd.GetOptionValues(&opts)
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
return run_func(cmd, &opts, args)
|
||||
},
|
||||
Hidden: true,
|
||||
})
|
||||
specialize_command(ans)
|
||||
clone := root.AddClone(ans.Group, ans)
|
||||
clone.Hidden = false
|
||||
clone.Name = "hyperlinked-grep"
|
||||
}
|
||||
|
||||
func EntryPoint(parent *cli.Command) {
|
||||
create_cmd(parent, main)
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ func DetectSupport(timeout time.Duration) (memory, files, direct bool, err error
|
||||
temp_files_to_delete := make([]string, 0, 8)
|
||||
shm_files_to_delete := make([]shm.MMap, 0, 8)
|
||||
var direct_query_id, file_query_id, memory_query_id uint32
|
||||
lp, e := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoMouseTracking, loop.NoInBandResizeNotifications)
|
||||
lp, e := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoMouseTracking)
|
||||
if e != nil {
|
||||
err = e
|
||||
return
|
||||
|
||||
@@ -292,7 +292,7 @@ func main(cmd *cli.Command, o *Options, args []string) (rc int, err error) {
|
||||
if imgd.err != nil {
|
||||
print_error("Failed to process \x1b[31m%s\x1b[39m: %s\r\n", imgd.source_name, imgd.err)
|
||||
} else {
|
||||
transmit_image(imgd, opts.NoTrailingNewline)
|
||||
transmit_image(imgd)
|
||||
if imgd.err != nil {
|
||||
print_error("Failed to transmit \x1b[31m%s\x1b[39m: %s\r\n", imgd.source_name, imgd.err)
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ Use the Unicode placeholder method to display the images. Useful to display
|
||||
images from within full screen terminal programs that do not understand the
|
||||
kitty graphics protocol such as multiplexers or editors. See
|
||||
:ref:`graphics_unicode_placeholders` for details. Note that when using this
|
||||
method, images placed (with :option:`--place`) that do not fit on the screen,
|
||||
method, placed (with :option:`--place`) images that do not fit on the screen,
|
||||
will get wrapped at the screen edge instead of getting truncated. This
|
||||
wrapping is per line and therefore the image will look like it is interleaved
|
||||
with blank lines.
|
||||
@@ -155,12 +155,6 @@ default=0
|
||||
The graphics protocol id to use for the created image. Normally, a random id is created if needed.
|
||||
This option allows control of the id. When multiple images are sent, sequential ids starting from the specified id
|
||||
are used. Valid ids are from 1 to 4294967295. Numbers outside this range are automatically wrapped.
|
||||
|
||||
|
||||
--no-trailing-newline -n
|
||||
type=bool-set
|
||||
By default, the cursor is moved to the next line after displaying an image. This option, prevents that. Should not be used
|
||||
when catting multiple images. Also has no effect when the :option:`--place` option is used.
|
||||
'''
|
||||
|
||||
help_text = (
|
||||
|
||||
@@ -272,15 +272,13 @@ func write_unicode_placeholder(imgd *image_data) {
|
||||
for c := 0; c < imgd.width_cells; c++ {
|
||||
os.Stdout.WriteString(string(kitty.ImagePlaceholderChar) + string(images.NumberToDiacritic[r]) + string(images.NumberToDiacritic[c]) + id_char)
|
||||
}
|
||||
if r < imgd.height_cells-1 {
|
||||
os.Stdout.WriteString("\n\r")
|
||||
}
|
||||
os.Stdout.WriteString("\n\r")
|
||||
}
|
||||
}
|
||||
|
||||
var seen_image_ids *utils.Set[uint32]
|
||||
|
||||
func transmit_image(imgd *image_data, no_trailing_newline bool) {
|
||||
func transmit_image(imgd *image_data) {
|
||||
if seen_image_ids == nil {
|
||||
seen_image_ids = utils.NewSet[uint32](32)
|
||||
}
|
||||
@@ -408,7 +406,7 @@ func transmit_image(imgd *image_data, no_trailing_newline bool) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if imgd.move_to.x == 0 && !no_trailing_newline {
|
||||
if imgd.move_to.x == 0 {
|
||||
fmt.Println() // ensure cursor is on new line
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,352 +0,0 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"image"
|
||||
"io"
|
||||
"os"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"kitty/tools/cli"
|
||||
"kitty/tools/tty"
|
||||
"kitty/tools/tui/loop"
|
||||
"kitty/tools/utils"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
const ESC_CODE_PREFIX = "\x1b]99;"
|
||||
const ESC_CODE_SUFFIX = "\x1b\\"
|
||||
const CHUNK_SIZE = 4096
|
||||
|
||||
func b64encode(x string) string {
|
||||
return base64.RawStdEncoding.EncodeToString(utils.UnsafeStringToBytes(x))
|
||||
}
|
||||
|
||||
func check_id_valid(x string) bool {
|
||||
pat := utils.MustCompile(`[^a-zA-Z0-9_+.-]`)
|
||||
return pat.ReplaceAllString(x, "") == x
|
||||
}
|
||||
|
||||
type parsed_data struct {
|
||||
opts *Options
|
||||
wait_till_closed bool
|
||||
expire_time time.Duration
|
||||
title, body, identifier string
|
||||
image_data []byte
|
||||
initial_msg string
|
||||
}
|
||||
|
||||
func (p *parsed_data) create_metadata() string {
|
||||
ans := []string{}
|
||||
if p.opts.AppName != "" {
|
||||
ans = append(ans, "f="+b64encode(p.opts.AppName))
|
||||
}
|
||||
switch p.opts.Urgency {
|
||||
case "low":
|
||||
ans = append(ans, "u=0")
|
||||
case "critical":
|
||||
ans = append(ans, "u=2")
|
||||
}
|
||||
if p.expire_time >= 0 {
|
||||
ans = append(ans, "w="+strconv.FormatInt(p.expire_time.Milliseconds(), 10))
|
||||
}
|
||||
if p.opts.Type != "" {
|
||||
ans = append(ans, "t="+b64encode(p.opts.Type))
|
||||
}
|
||||
if p.wait_till_closed {
|
||||
ans = append(ans, "c=1:a=report")
|
||||
}
|
||||
for _, x := range p.opts.Icon {
|
||||
ans = append(ans, "n="+b64encode(x))
|
||||
}
|
||||
if p.opts.IconCacheId != "" {
|
||||
ans = append(ans, "g="+p.opts.IconCacheId)
|
||||
}
|
||||
if p.opts.SoundName != "system" {
|
||||
ans = append(ans, "s="+b64encode(p.opts.SoundName))
|
||||
}
|
||||
m := strings.Join(ans, ":")
|
||||
if m != "" {
|
||||
m = ":" + m
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
var debugprintln = tty.DebugPrintln
|
||||
|
||||
func (p *parsed_data) generate_chunks(callback func(string)) {
|
||||
prefix := ESC_CODE_PREFIX + "i=" + p.identifier
|
||||
write_chunk := func(middle string) {
|
||||
callback(prefix + middle + ESC_CODE_SUFFIX)
|
||||
}
|
||||
|
||||
add_payload := func(payload_type, payload string) {
|
||||
if payload == "" {
|
||||
return
|
||||
}
|
||||
p := utils.IfElse(payload_type == "title", "", ":p="+payload_type)
|
||||
payload = b64encode(payload)
|
||||
for len(payload) > 0 {
|
||||
chunk := payload[:min(CHUNK_SIZE, len(payload))]
|
||||
payload = utils.IfElse(len(payload) > len(chunk), payload[len(chunk):], "")
|
||||
write_chunk(":d=0:e=1" + p + ";" + chunk)
|
||||
}
|
||||
}
|
||||
metadata := p.create_metadata()
|
||||
write_chunk(":d=0" + metadata + ";")
|
||||
add_payload("title", p.title)
|
||||
add_payload("body", p.body)
|
||||
if len(p.image_data) > 0 {
|
||||
add_payload("icon", utils.UnsafeBytesToString(p.image_data))
|
||||
}
|
||||
if len(p.opts.Button) > 0 {
|
||||
add_payload("buttons", strings.Join(p.opts.Button, "\u2028"))
|
||||
}
|
||||
write_chunk(";")
|
||||
}
|
||||
|
||||
func (p *parsed_data) run_loop() (err error) {
|
||||
lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoMouseTracking, loop.NoInBandResizeNotifications)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
activated := -1
|
||||
prefix := ESC_CODE_PREFIX + "i=" + p.identifier
|
||||
|
||||
poll_for_close := func() {
|
||||
lp.AddTimer(time.Millisecond*50, false, func(_ loop.IdType) error {
|
||||
lp.QueueWriteString(prefix + ":p=alive;" + ESC_CODE_SUFFIX)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
lp.OnInitialize = func() (string, error) {
|
||||
if p.initial_msg != "" {
|
||||
return p.initial_msg, nil
|
||||
}
|
||||
p.generate_chunks(func(x string) { lp.QueueWriteString(x) })
|
||||
return "", nil
|
||||
}
|
||||
lp.OnEscapeCode = func(ect loop.EscapeCodeType, data []byte) error {
|
||||
if ect == loop.OSC && bytes.HasPrefix(data, []byte(ESC_CODE_PREFIX[2:])) {
|
||||
raw := utils.UnsafeBytesToString(data[len(ESC_CODE_PREFIX[2:]):])
|
||||
metadata, payload, _ := strings.Cut(raw, ";")
|
||||
sent_identifier, payload_type := "", ""
|
||||
for _, x := range strings.Split(metadata, ":") {
|
||||
key, val, _ := strings.Cut(x, "=")
|
||||
switch key {
|
||||
case "i":
|
||||
sent_identifier = val
|
||||
case "p":
|
||||
payload_type = val
|
||||
}
|
||||
}
|
||||
if sent_identifier == p.identifier {
|
||||
switch payload_type {
|
||||
case "close":
|
||||
if payload == "untracked" {
|
||||
poll_for_close()
|
||||
} else {
|
||||
lp.Quit(0)
|
||||
}
|
||||
case "alive":
|
||||
live_ids := strings.Split(payload, ",")
|
||||
if slices.Contains(live_ids, p.identifier) {
|
||||
poll_for_close()
|
||||
} else {
|
||||
lp.Quit(0)
|
||||
}
|
||||
case "":
|
||||
if activated, err = strconv.Atoi(utils.IfElse(payload == "", "0", payload)); err != nil {
|
||||
return fmt.Errorf("Got invalid activation response from terminal: %#v", payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
close_requested := 0
|
||||
lp.OnKeyEvent = func(event *loop.KeyEvent) error {
|
||||
if event.MatchesPressOrRepeat("ctrl+c") || event.MatchesPressOrRepeat("esc") {
|
||||
event.Handled = true
|
||||
switch close_requested {
|
||||
case 0:
|
||||
lp.QueueWriteString(prefix + ":p=close;" + ESC_CODE_SUFFIX)
|
||||
lp.Println("Closing notification, please wait...")
|
||||
close_requested++
|
||||
case 1:
|
||||
key := "Esc"
|
||||
if event.MatchesPressOrRepeat("ctrl+c") {
|
||||
key = "Ctrl+C"
|
||||
}
|
||||
lp.Println(fmt.Sprintf("Waiting for response from terminal, press the %s key again to abort. Note that this might result in garbage being printed to the terminal.", key))
|
||||
close_requested++
|
||||
default:
|
||||
return fmt.Errorf("Aborted by user!")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
err = lp.Run()
|
||||
ds := lp.DeathSignalName()
|
||||
if ds != "" {
|
||||
fmt.Println("Killed by signal: ", ds)
|
||||
lp.KillIfSignalled()
|
||||
return
|
||||
}
|
||||
if activated > -1 && err == nil {
|
||||
fmt.Println(activated)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func random_ident() (string, error) {
|
||||
return utils.HumanUUID4()
|
||||
}
|
||||
|
||||
func parse_duration(x string) (ans time.Duration, err error) {
|
||||
switch x {
|
||||
case "never":
|
||||
return 0, nil
|
||||
case "":
|
||||
return -1, nil
|
||||
}
|
||||
trailer := x[len(x)-1]
|
||||
multipler := time.Second
|
||||
switch trailer {
|
||||
case 's':
|
||||
x = x[:len(x)-1]
|
||||
case 'm':
|
||||
x = x[:len(x)-1]
|
||||
multipler = time.Minute
|
||||
case 'h':
|
||||
x = x[:len(x)-1]
|
||||
multipler = time.Hour
|
||||
case 'd':
|
||||
x = x[:len(x)-1]
|
||||
multipler = time.Hour * 24
|
||||
}
|
||||
val, err := strconv.ParseFloat(x, 64)
|
||||
if err != nil {
|
||||
return ans, err
|
||||
}
|
||||
ans = time.Duration(float64(multipler) * val)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parsed_data) load_image_data() (err error) {
|
||||
if p.opts.IconPath == "" {
|
||||
return nil
|
||||
}
|
||||
f, err := os.Open(p.opts.IconPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
_, imgfmt, err := image.DecodeConfig(f)
|
||||
if _, err = f.Seek(0, io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
if err == nil && imgfmt != "" && strings.Contains("jpeg jpg gif png", strings.ToLower(imgfmt)) {
|
||||
p.image_data, err = io.ReadAll(f)
|
||||
return
|
||||
}
|
||||
return fmt.Errorf("The icon must be in PNG, JPEG or GIF formats")
|
||||
}
|
||||
|
||||
func main(_ *cli.Command, opts *Options, args []string) (rc int, err error) {
|
||||
if len(args) == 0 {
|
||||
return 1, fmt.Errorf("Must specify a TITLE for the notification")
|
||||
}
|
||||
var p parsed_data
|
||||
p.opts = opts
|
||||
p.title = args[0]
|
||||
if len(args) > 1 {
|
||||
p.body = strings.Join(args[1:], " ")
|
||||
}
|
||||
ident := opts.Identifier
|
||||
if ident == "" {
|
||||
if ident, err = random_ident(); err != nil {
|
||||
return 1, fmt.Errorf("Failed to generate a random identifier with error: %w", err)
|
||||
}
|
||||
}
|
||||
bad_ident := func(which string) error {
|
||||
return fmt.Errorf("Invalid identifier: %s must be only English letters, numbers, hyphens and underscores.", which)
|
||||
}
|
||||
if !check_id_valid(ident) {
|
||||
return 1, bad_ident(ident)
|
||||
}
|
||||
p.identifier = ident
|
||||
if !check_id_valid(opts.IconCacheId) {
|
||||
return 1, bad_ident(opts.IconCacheId)
|
||||
}
|
||||
if len(p.title) == 0 {
|
||||
if ident == "" {
|
||||
return 1, fmt.Errorf("Must specify a non-empty TITLE for the notification or specify an identifier to close a notification.")
|
||||
}
|
||||
msg := ESC_CODE_PREFIX + "i=" + ident + ":p=close;" + ESC_CODE_SUFFIX
|
||||
if opts.OnlyPrintEscapeCode {
|
||||
_, err = os.Stdout.WriteString(msg)
|
||||
} else if p.wait_till_closed {
|
||||
p.initial_msg = msg
|
||||
err = p.run_loop()
|
||||
} else {
|
||||
var term *tty.Term
|
||||
if term, err = tty.OpenControllingTerm(); err != nil {
|
||||
return 1, fmt.Errorf("Failed to open controlling terminal with error: %w", err)
|
||||
}
|
||||
if _, err = term.WriteString(msg); err != nil {
|
||||
term.RestoreAndClose()
|
||||
return 1, err
|
||||
}
|
||||
term.RestoreAndClose()
|
||||
}
|
||||
}
|
||||
if p.expire_time, err = parse_duration(opts.ExpireAfter); err != nil {
|
||||
return 1, fmt.Errorf("Invalid expire time: %s with error: %w", opts.ExpireAfter, err)
|
||||
}
|
||||
p.wait_till_closed = opts.WaitTillClosed
|
||||
if err = p.load_image_data(); err != nil {
|
||||
return 1, fmt.Errorf("Failed to load image data from %s with error %w", opts.IconPath, err)
|
||||
}
|
||||
if opts.OnlyPrintEscapeCode {
|
||||
p.generate_chunks(func(x string) {
|
||||
if err == nil {
|
||||
_, err = os.Stdout.WriteString(x)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if opts.PrintIdentifier {
|
||||
fmt.Println(ident)
|
||||
}
|
||||
if p.wait_till_closed {
|
||||
err = p.run_loop()
|
||||
} else {
|
||||
var term *tty.Term
|
||||
if term, err = tty.OpenControllingTerm(); err != nil {
|
||||
return 1, fmt.Errorf("Failed to open controlling terminal with error: %w", err)
|
||||
}
|
||||
p.generate_chunks(func(x string) {
|
||||
if err == nil {
|
||||
_, err = term.WriteString(x)
|
||||
}
|
||||
})
|
||||
term.RestoreAndClose()
|
||||
}
|
||||
|
||||
}
|
||||
if err != nil {
|
||||
rc = 1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func EntryPoint(parent *cli.Command) {
|
||||
create_cmd(parent, main)
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
def OPTIONS() -> str:
|
||||
from kitty.constants import standard_icon_names
|
||||
return f'''
|
||||
--icon -n
|
||||
type=list
|
||||
The name of the icon to use for the notification. An icon with this name
|
||||
will be searched for on the computer running the terminal emulator. Can
|
||||
be specified multiple times, the first name that is found will be used.
|
||||
Standard names: {', '.join(sorted(standard_icon_names))}
|
||||
|
||||
|
||||
--icon-path -p
|
||||
Path to an image file in PNG/JPEG/GIF formats to use as the icon. If both
|
||||
name and path are specified then first the name will be looked for and if not found
|
||||
then the path will be used.
|
||||
|
||||
|
||||
--app-name -a
|
||||
default=kitten-notify
|
||||
The application name for the notification.
|
||||
|
||||
|
||||
--button -b
|
||||
type=list
|
||||
Add a button with the specified text to the notification. Can be specified multiple times for multiple buttons.
|
||||
If --wait-till-closed is used then the kitten will print the button number to STDOUT if the user clicks a button.
|
||||
1 for the first button, 2 for the second button and so on.
|
||||
|
||||
|
||||
--urgency -u
|
||||
default=normal
|
||||
choices=normal,low,critical
|
||||
The urgency of the notification.
|
||||
|
||||
|
||||
--expire-after -e
|
||||
The duration, for the notification to appear on screen. The default is to
|
||||
use the policy of the OS notification service. A value of :code:`never` means the notification should
|
||||
never expire, however, this may or may not work depending on the policies of the OS notification
|
||||
service. Time is specified in the form NUMBER[SUFFIX] where SUFFIX can be :code:`s` for seconds, :code:`m` for minutes,
|
||||
:code:`h` for hours or :code:`d` for days. Non-integer numbers are allowed.
|
||||
If not specified, seconds is assumed. The notification is guaranteed to be closed automatically
|
||||
after the specified time has elapsed. The notification could be closed before by user
|
||||
action or OS policy.
|
||||
|
||||
|
||||
--sound-name -s
|
||||
default=system
|
||||
The name of the sound to play with the notification. :code:`system` means let the
|
||||
notification system use whatever sound it wants. :code:`silent` means prevent
|
||||
any sound from being played. Any other value is passed to the desktop's notification system
|
||||
which may or may not honor it.
|
||||
|
||||
|
||||
--type -t
|
||||
The notification type. Can be any string, it is used by users to create filter rules
|
||||
for notifications, so choose something descriptive of the notification's purpose.
|
||||
|
||||
|
||||
--identifier -i
|
||||
The identifier of this notification. If a notification with the same identifier
|
||||
is already displayed, it is replaced/updated.
|
||||
|
||||
|
||||
--print-identifier -P
|
||||
type=bool-set
|
||||
Print the identifier for the notification to STDOUT. Useful when not specifying
|
||||
your own identifier via the --identifier option.
|
||||
|
||||
|
||||
--wait-till-closed --wait-for-completion -w
|
||||
type=bool-set
|
||||
Wait until the notification is closed. If the user activates the notification,
|
||||
"0" is printed to STDOUT before quitting. If a button on the notification is pressed the
|
||||
number corresponding to the button is printed to STDOUT. Press the Esc or Ctrl+C keys
|
||||
to close the notification manually.
|
||||
|
||||
|
||||
--only-print-escape-code
|
||||
type=bool-set
|
||||
Only print the escape code to STDOUT. Useful if using this kitten as part
|
||||
of a larger application. If this is specified, the --wait-till-closed option
|
||||
will be used for escape code generation, but no actual waiting will be done.
|
||||
|
||||
|
||||
--icon-cache-id -g
|
||||
Identifier to use when caching icons in the terminal emulator. Using an identifier means
|
||||
that icon data needs to be transmitted only once using --icon-path. Subsequent invocations
|
||||
will use the cached icon data, at least until the terminal instance is restarted. This is useful
|
||||
if this kitten is being used inside a larger application, with --only-print-escape-code.
|
||||
'''
|
||||
|
||||
help_text = '''\
|
||||
Send notifications to the user that are displayed to them via the
|
||||
desktop environment's notifications service. Works over SSH as well.
|
||||
|
||||
To update an existing notification, specify the identifier of the notification
|
||||
with the --identifier option. The value should be the same as the identifier specified for
|
||||
the notification you wish to update.
|
||||
|
||||
If no title is specified and an identifier is specified using the --identifier
|
||||
option, then instead of creating a new notification, an existing notification
|
||||
with the specified identifier is closed.
|
||||
'''
|
||||
|
||||
usage = 'TITLE [BODY ...]'
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit('This should be run as `kitten notify ...`')
|
||||
elif __name__ == '__doc__':
|
||||
cd = sys.cli_docs # type: ignore
|
||||
cd['usage'] = usage
|
||||
cd['options'] = OPTIONS
|
||||
cd['help_text'] = help_text
|
||||
cd['short_desc'] = 'Send notifications to the user'
|
||||
@@ -10,16 +10,10 @@ from kitty.constants import appname, is_macos, is_wayland
|
||||
from kitty.fast_data_types import (
|
||||
GLFW_EDGE_BOTTOM,
|
||||
GLFW_EDGE_LEFT,
|
||||
GLFW_EDGE_NONE,
|
||||
GLFW_EDGE_RIGHT,
|
||||
GLFW_EDGE_TOP,
|
||||
GLFW_FOCUS_EXCLUSIVE,
|
||||
GLFW_FOCUS_NOT_ALLOWED,
|
||||
GLFW_FOCUS_ON_DEMAND,
|
||||
GLFW_LAYER_SHELL_BACKGROUND,
|
||||
GLFW_LAYER_SHELL_OVERLAY,
|
||||
GLFW_LAYER_SHELL_PANEL,
|
||||
GLFW_LAYER_SHELL_TOP,
|
||||
glfw_primary_monitor_size,
|
||||
make_x11_window_a_dock_window,
|
||||
)
|
||||
@@ -28,48 +22,14 @@ from kitty.types import LayerShellConfig
|
||||
from kitty.typing import EdgeLiteral
|
||||
|
||||
OPTIONS = r'''
|
||||
--lines
|
||||
--lines --columns
|
||||
type=int
|
||||
default=1
|
||||
The number of lines shown in the panel. Ignored for background and vertical panels.
|
||||
|
||||
|
||||
--columns
|
||||
type=int
|
||||
default=1
|
||||
The number of columns shown in the panel. Ignored for background and horizontal panels.
|
||||
|
||||
|
||||
--margin-top
|
||||
type=int
|
||||
default=0
|
||||
Request a given top margin to the compositor.
|
||||
Only works on a Wayland compositor that supports the wlr layer shell protocol.
|
||||
|
||||
|
||||
--margin-left
|
||||
type=int
|
||||
default=0
|
||||
Request a given left margin to the compositor.
|
||||
Only works on a Wayland compositor that supports the wlr layer shell protocol.
|
||||
|
||||
|
||||
--margin-bottom
|
||||
type=int
|
||||
default=0
|
||||
Request a given bottom margin to the compositor.
|
||||
Only works on a Wayland compositor that supports the wlr layer shell protocol.
|
||||
|
||||
|
||||
--margin-right
|
||||
type=int
|
||||
default=0
|
||||
Request a given right margin to the compositor.
|
||||
Only works on a Wayland compositor that supports the wlr layer shell protocol.
|
||||
The number of lines shown in the panel if horizontal otherwise the number of columns shown in the panel. Ignored for background panels.
|
||||
|
||||
|
||||
--edge
|
||||
choices=top,bottom,left,right,background,none
|
||||
choices=top,bottom,left,right,background
|
||||
default=top
|
||||
Which edge of the screen to place the panel on. Note that some window managers
|
||||
(such as i3) do not support placing docked windows on the left and right edges.
|
||||
@@ -77,16 +37,6 @@ The value :code:`background` means make the panel the "desktop wallpaper". This
|
||||
is only supported on Wayland, not X11 and note that when using sway if you set
|
||||
a background in your sway config it will cover the background drawn using this
|
||||
kitten.
|
||||
The value :code:`none` anchors the panel to the top left corner by default
|
||||
and the panel should be placed using margins parameters.
|
||||
|
||||
|
||||
--layer
|
||||
choices=background,bottom,top,overlay
|
||||
default=bottom
|
||||
On a Wayland compositor that supports the wlr layer shell protocol, specifies the layer
|
||||
on which the panel should be drawn. This parameter is ignored and set to
|
||||
:code:`background` if :option:`--edge` is set to :code:`background`.
|
||||
|
||||
|
||||
--config -c
|
||||
@@ -118,29 +68,6 @@ condition=not is_macos
|
||||
Set the name part of the :italic:`WM_CLASS` property (defaults to using the value from :option:`{appname} --class`)
|
||||
|
||||
|
||||
--focus-policy
|
||||
choices=not-allowed,exclusive,on-demand
|
||||
default=not-allowed
|
||||
On a Wayland compositor that supports the wlr layer shell protocol, specify the focus policy for keyboard
|
||||
interactivity with the panel. Please refer to the wlr layer shell protocol documentation for more details.
|
||||
|
||||
|
||||
--exclusive-zone
|
||||
type=int
|
||||
default=-1
|
||||
On a Wayland compositor that supports the wlr layer shell protocol, request a given exclusive zone for the panel.
|
||||
Please refer to the wlr layer shell documentation for more details on the meaning of exclusive and its value.
|
||||
If :option:`--edge` is set to anything else than :code:`none`, this flag will not have any effect unless
|
||||
the flag :option:`--override-exclusive-zone` is also set.
|
||||
If :option:`--edge` is set to :code:`background`, this option has no effect.
|
||||
|
||||
|
||||
--override-exclusive-zone
|
||||
type=bool-set
|
||||
On a Wayland compositor that supports the wlr layer shell protocol, override the default exclusive zone.
|
||||
This has effect only if :option:`--edge` is set to :code:`top`, :code:`left`, :code:`bottom` or :code:`right`.
|
||||
|
||||
|
||||
--debug-rendering
|
||||
type=bool-set
|
||||
For internal debugging use.
|
||||
@@ -223,29 +150,9 @@ def initial_window_size_func(opts: WindowSizeData, cached_values: Dict[str, Any]
|
||||
|
||||
|
||||
def layer_shell_config(opts: PanelCLIOptions) -> LayerShellConfig:
|
||||
ltype = {'background': GLFW_LAYER_SHELL_BACKGROUND,
|
||||
'bottom': GLFW_LAYER_SHELL_PANEL,
|
||||
'top': GLFW_LAYER_SHELL_TOP,
|
||||
'overlay': GLFW_LAYER_SHELL_OVERLAY}.get(opts.layer, GLFW_LAYER_SHELL_PANEL)
|
||||
ltype = GLFW_LAYER_SHELL_BACKGROUND if opts.edge == 'background' else ltype
|
||||
edge = {
|
||||
'top': GLFW_EDGE_TOP, 'bottom': GLFW_EDGE_BOTTOM, 'left': GLFW_EDGE_LEFT, 'right': GLFW_EDGE_RIGHT, 'none': GLFW_EDGE_NONE
|
||||
}.get(opts.edge, GLFW_EDGE_TOP)
|
||||
focus_policy = {
|
||||
'not-allowed': GLFW_FOCUS_NOT_ALLOWED, 'exclusive': GLFW_FOCUS_EXCLUSIVE, 'on-demand': GLFW_FOCUS_ON_DEMAND
|
||||
}.get(opts.focus_policy, GLFW_FOCUS_NOT_ALLOWED)
|
||||
return LayerShellConfig(type=ltype,
|
||||
edge=edge,
|
||||
x_size_in_cells=max(1, opts.columns),
|
||||
y_size_in_cells=max(1, opts.lines),
|
||||
requested_top_margin=max(0, opts.margin_top),
|
||||
requested_left_margin=max(0, opts.margin_left),
|
||||
requested_bottom_margin=max(0, opts.margin_bottom),
|
||||
requested_right_margin=max(0, opts.margin_right),
|
||||
focus_policy=focus_policy,
|
||||
requested_exclusive_zone=opts.exclusive_zone,
|
||||
override_exclusive_zone=opts.override_exclusive_zone,
|
||||
output_name=opts.output_name or '')
|
||||
ltype = GLFW_LAYER_SHELL_BACKGROUND if opts.edge == 'background' else GLFW_LAYER_SHELL_PANEL
|
||||
edge = {'top': GLFW_EDGE_TOP, 'bottom': GLFW_EDGE_BOTTOM, 'left': GLFW_EDGE_LEFT, 'right': GLFW_EDGE_RIGHT}.get(opts.edge, GLFW_EDGE_TOP)
|
||||
return LayerShellConfig(type=ltype, edge=edge, size_in_cells=max(1, opts.lines), output_name=opts.output_name or '')
|
||||
|
||||
|
||||
def main(sys_args: List[str]) -> None:
|
||||
|
||||
@@ -26,7 +26,7 @@ func main(cmd *cli.Command, opts *Options, args []string) (rc int, err error) {
|
||||
queries[i] = x
|
||||
}
|
||||
}
|
||||
lp, err := loop.New(loop.NoAlternateScreen, loop.NoKeyboardStateChange, loop.NoMouseTracking, loop.NoRestoreColors, loop.NoInBandResizeNotifications)
|
||||
lp, err := loop.New(loop.NoAlternateScreen, loop.NoKeyboardStateChange, loop.NoMouseTracking, loop.NoRestoreColors)
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
|
||||
@@ -179,12 +179,14 @@ class Foreground(Query):
|
||||
|
||||
@staticmethod
|
||||
def get_result(opts: Options, window_id: int, os_window_id: int) -> str:
|
||||
from kitty.fast_data_types import get_boss, get_options
|
||||
from kitty.fast_data_types import Color, get_boss
|
||||
boss = get_boss()
|
||||
w = boss.window_id_map.get(window_id)
|
||||
if w is None:
|
||||
return opts.foreground.as_sharp
|
||||
return (w.screen.color_profile.default_fg or get_options().foreground).as_sharp
|
||||
col = w.screen.color_profile.default_fg
|
||||
r, g, b = col >> 16, (col >> 8) & 0xff, col & 0xff
|
||||
return Color(r, g, b).as_sharp
|
||||
|
||||
|
||||
@query
|
||||
@@ -194,27 +196,16 @@ class Background(Query):
|
||||
|
||||
@staticmethod
|
||||
def get_result(opts: Options, window_id: int, os_window_id: int) -> str:
|
||||
from kitty.fast_data_types import get_boss, get_options
|
||||
from kitty.fast_data_types import Color, get_boss
|
||||
boss = get_boss()
|
||||
w = boss.window_id_map.get(window_id)
|
||||
if w is None:
|
||||
return opts.background.as_sharp
|
||||
return (w.screen.color_profile.default_bg or get_options().background).as_sharp
|
||||
col = w.screen.color_profile.default_bg
|
||||
r, g, b = col >> 16, (col >> 8) & 0xff, col & 0xff
|
||||
return Color(r, g, b).as_sharp
|
||||
|
||||
|
||||
@query
|
||||
class BackgroundOpacity(Query):
|
||||
name: str = 'background_opacity'
|
||||
help_text: str = 'The current background opacity as a number between 0 and 1'
|
||||
|
||||
@staticmethod
|
||||
def get_result(opts: Options, window_id: int, os_window_id: int) -> str:
|
||||
from kitty.fast_data_types import background_opacity_of
|
||||
ans = background_opacity_of(os_window_id)
|
||||
if ans is None:
|
||||
ans = 1.0
|
||||
return f'{ans:g}'
|
||||
|
||||
|
||||
@query
|
||||
class ClipboardControl(Query):
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user