Compare commits
335 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e3c60f43f | ||
|
|
b03dd5b6b2 | ||
|
|
2aa9886ebb | ||
|
|
225e52b8b9 | ||
|
|
0ae1f9906f | ||
|
|
a5918b50f8 | ||
|
|
fa0374ee61 | ||
|
|
5b1e1aee3d | ||
|
|
3ba11d08fb | ||
|
|
35d0f42d07 | ||
|
|
f08f222337 | ||
|
|
7c4ad278d5 | ||
|
|
d3d2930bd2 | ||
|
|
b571c20acc | ||
|
|
f6cc0302c4 | ||
|
|
d9abcf0c83 | ||
|
|
5346849439 | ||
|
|
9742b0c5b1 | ||
|
|
41cfe26898 | ||
|
|
05a254eee9 | ||
|
|
8ef53aa363 | ||
|
|
470abc25e4 | ||
|
|
7fe6c7e78b | ||
|
|
6c8a4f8d9f | ||
|
|
2a8c8c0cbb | ||
|
|
12bbe5459d | ||
|
|
e0d9e05fd4 | ||
|
|
8bea61cb99 | ||
|
|
d09666aba9 | ||
|
|
48e0ba1d3d | ||
|
|
fb72be41f4 | ||
|
|
ef1db6d089 | ||
|
|
9d4246a285 | ||
|
|
e0f5c39297 | ||
|
|
e4d353b105 | ||
|
|
0d665495b8 | ||
|
|
fdaf857885 | ||
|
|
987b41d2cb | ||
|
|
444080f320 | ||
|
|
b5007ba276 | ||
|
|
bf19d2b156 | ||
|
|
a78515e5bf | ||
|
|
581126c748 | ||
|
|
7b3e345a2a | ||
|
|
cc7cefd3ed | ||
|
|
de6528b7d8 | ||
|
|
9efdfe0de4 | ||
|
|
f936918278 | ||
|
|
00125577a9 | ||
|
|
083c47b0f0 | ||
|
|
8021686154 | ||
|
|
f55ca296da | ||
|
|
0d6bca3e5d | ||
|
|
be1ff61e4a | ||
|
|
e36d41b46f | ||
|
|
b4693dc1b3 | ||
|
|
ca6010789e | ||
|
|
f8a80ccf5f | ||
|
|
6461dccbdc | ||
|
|
b06f4f2574 | ||
|
|
7695e3a9c8 | ||
|
|
ae475b408f | ||
|
|
6d9d048287 | ||
|
|
09e4d101c5 | ||
|
|
36340caf4c | ||
|
|
9a80ab0700 | ||
|
|
339d972d8d | ||
|
|
6d1e1030bd | ||
|
|
8a3665ce55 | ||
|
|
8351f0beab | ||
|
|
c26808c59e | ||
|
|
50b1dfc746 | ||
|
|
2ec8ec2327 | ||
|
|
168480b0ee | ||
|
|
58f78afc2e | ||
|
|
d60020f5ac | ||
|
|
437efe5473 | ||
|
|
5737904cec | ||
|
|
431a8af08b | ||
|
|
be29c4a243 | ||
|
|
0a027fad9a | ||
|
|
e21a8e3cc2 | ||
|
|
4279d6514d | ||
|
|
457db5994b | ||
|
|
71c942d290 | ||
|
|
ee5bc8523b | ||
|
|
70f4112643 | ||
|
|
174f6b7ea8 | ||
|
|
b14fb6f3c4 | ||
|
|
d6e27e776b | ||
|
|
e6839b45e3 | ||
|
|
229134cf31 | ||
|
|
b552173185 | ||
|
|
399a1f8fee | ||
|
|
9293d9b0ed | ||
|
|
4a75952fc7 | ||
|
|
c19906eb87 | ||
|
|
00ea189074 | ||
|
|
c8e2061e2a | ||
|
|
78dc93721d | ||
|
|
33beecddda | ||
|
|
e99d93ca30 | ||
|
|
0cc54484a4 | ||
|
|
4df7d59de0 | ||
|
|
9a54da84dc | ||
|
|
83315f7999 | ||
|
|
eb5ebfd304 | ||
|
|
77c9124a3d | ||
|
|
317da76ced | ||
|
|
e8d8ced006 | ||
|
|
0321f9bddf | ||
|
|
5937892ebc | ||
|
|
e429bea2f4 | ||
|
|
d33ad5c616 | ||
|
|
3e0614a293 | ||
|
|
80e39212b8 | ||
|
|
d7a6ceb3a6 | ||
|
|
9d3a9e9d1e | ||
|
|
e95c789d4b | ||
|
|
a508161265 | ||
|
|
a935c80adc | ||
|
|
1d607bf13e | ||
|
|
ad57467c93 | ||
|
|
430eaefc3e | ||
|
|
3d60724651 | ||
|
|
31d9f663fc | ||
|
|
f324697c7c | ||
|
|
befae7e677 | ||
|
|
7a2c2587ba | ||
|
|
cf3f9d68b3 | ||
|
|
c99f7c554f | ||
|
|
f01c0945da | ||
|
|
1bcd0c4471 | ||
|
|
07f28e6ec7 | ||
|
|
7074f9f776 | ||
|
|
5029a9ad5a | ||
|
|
0bff30e954 | ||
|
|
601d37aa3d | ||
|
|
e2b1ca10c9 | ||
|
|
11942ed6dc | ||
|
|
f65914599b | ||
|
|
392c31f5fe | ||
|
|
f4ddaacb3c | ||
|
|
db64aef666 | ||
|
|
95c8bbd7fb | ||
|
|
67b4f37d53 | ||
|
|
427288c16b | ||
|
|
4202eee084 | ||
|
|
eca53bfab0 | ||
|
|
cd76d109f5 | ||
|
|
2787f9f19b | ||
|
|
4e3c6e52aa | ||
|
|
ae5ceedfe9 | ||
|
|
022e2eb1db | ||
|
|
2fe1ef4774 | ||
|
|
70afb2dd4d | ||
|
|
efa02e3f94 | ||
|
|
5eefd41059 | ||
|
|
6ca1b7c240 | ||
|
|
31191fd48c | ||
|
|
a6ed54ed48 | ||
|
|
95132b4fe1 | ||
|
|
df0c5d99e6 | ||
|
|
4b0724530e | ||
|
|
e5681a93f9 | ||
|
|
fb838a694e | ||
|
|
8c6f188767 | ||
|
|
55e15dc79e | ||
|
|
514073cb75 | ||
|
|
f6ece6522e | ||
|
|
817f0997cf | ||
|
|
1d651270d3 | ||
|
|
6ea58fac1a | ||
|
|
c7eb63d4ff | ||
|
|
4062365b19 | ||
|
|
7f48a927de | ||
|
|
4a740cc556 | ||
|
|
628b92f20b | ||
|
|
8f9616c230 | ||
|
|
df475dfde5 | ||
|
|
a835b56a51 | ||
|
|
5555a66638 | ||
|
|
dad355b932 | ||
|
|
90a097d45c | ||
|
|
dba01bc014 | ||
|
|
1d0a3ce998 | ||
|
|
076ad850bb | ||
|
|
868546abe4 | ||
|
|
6b421fabd1 | ||
|
|
ab3c9658bd | ||
|
|
8c7886d429 | ||
|
|
110a3e7e0f | ||
|
|
ee0b70f51b | ||
|
|
df48061a8f | ||
|
|
b1387d50f0 | ||
|
|
73dd218a95 | ||
|
|
ee4cedcc81 | ||
|
|
d1b2f9c6a9 | ||
|
|
40dee44978 | ||
|
|
db04c70d4a | ||
|
|
7401c6ac97 | ||
|
|
f7a1222cc3 | ||
|
|
069f09c43c | ||
|
|
35f821f6a1 | ||
|
|
67f60847ee | ||
|
|
65f6b142a5 | ||
|
|
81bda736d8 | ||
|
|
ec3998a9b8 | ||
|
|
01be77e0bc | ||
|
|
6f40b8d0a1 | ||
|
|
a083aa04b2 | ||
|
|
1f33348cf6 | ||
|
|
a45651933c | ||
|
|
ab57fbff73 | ||
|
|
6b3f774f05 | ||
|
|
1ef2880c6c | ||
|
|
ace152d8a3 | ||
|
|
8d08036a9d | ||
|
|
b0876fae64 | ||
|
|
01633776d3 | ||
|
|
e4202228eb | ||
|
|
79d5049273 | ||
|
|
9120e3b790 | ||
|
|
974e6140b3 | ||
|
|
1fd338cc6c | ||
|
|
bc61a81d7a | ||
|
|
06b30d1cc3 | ||
|
|
785973bb5e | ||
|
|
78efefcea1 | ||
|
|
1bb6242020 | ||
|
|
1af426bf84 | ||
|
|
dc4762a69a | ||
|
|
a785b77da9 | ||
|
|
1342767805 | ||
|
|
a3fc3233cd | ||
|
|
8093d70643 | ||
|
|
9d9ec84b54 | ||
|
|
6b26088c27 | ||
|
|
43af715caa | ||
|
|
c6eea488b1 | ||
|
|
b1c3999a81 | ||
|
|
4f19403518 | ||
|
|
4d1b537f78 | ||
|
|
dd54db47a9 | ||
|
|
44775e4644 | ||
|
|
805921d6a3 | ||
|
|
d7cd6edaa5 | ||
|
|
6a472bd6da | ||
|
|
d0c6ce9fd7 | ||
|
|
df48e2f9e2 | ||
|
|
2b4b3bbf18 | ||
|
|
6ea5027bf6 | ||
|
|
384ccb4fc7 | ||
|
|
ff763b099e | ||
|
|
741ae4c04b | ||
|
|
d84ec941c0 | ||
|
|
f0b5dd9799 | ||
|
|
cec4371b51 | ||
|
|
c0ff39c59d | ||
|
|
022634f2d9 | ||
|
|
392f576d07 | ||
|
|
6d59e2125e | ||
|
|
6476a1d2e0 | ||
|
|
ca998fb4b0 | ||
|
|
48631c4ea6 | ||
|
|
294be2a772 | ||
|
|
533688a67a | ||
|
|
4309e2371d | ||
|
|
ea30c84240 | ||
|
|
d4c2b13dda | ||
|
|
1decae2938 | ||
|
|
145ec86d3b | ||
|
|
e17c96b20f | ||
|
|
2cfcefd9eb | ||
|
|
356e950122 | ||
|
|
64dee63b73 | ||
|
|
431d013d32 | ||
|
|
40eb8455d0 | ||
|
|
c46b36509b | ||
|
|
f7ff63132d | ||
|
|
e17036a7a5 | ||
|
|
e678c41f56 | ||
|
|
ee3462592a | ||
|
|
521f921424 | ||
|
|
ee47a10b37 | ||
|
|
b81700144d | ||
|
|
9f28dd61d3 | ||
|
|
dad7d366b3 | ||
|
|
209b78a3ec | ||
|
|
17f34d1f95 | ||
|
|
26699cd9ef | ||
|
|
907db09a58 | ||
|
|
d506fa0565 | ||
|
|
de1a0d48a6 | ||
|
|
a20e5fefd3 | ||
|
|
44558560f9 | ||
|
|
677f154f03 | ||
|
|
0adaf064e7 | ||
|
|
3c828feef2 | ||
|
|
4d3c5e61fd | ||
|
|
67baefb52a | ||
|
|
d509a4fa8c | ||
|
|
cad1c12b7b | ||
|
|
9f751d8715 | ||
|
|
cf8a1dff33 | ||
|
|
973d98457f | ||
|
|
ce262f810d | ||
|
|
bd31c2b484 | ||
|
|
ef0e610802 | ||
|
|
3040d4c83f | ||
|
|
c9252e8639 | ||
|
|
e2e701460b | ||
|
|
f0c3641ebd | ||
|
|
025f6d7701 | ||
|
|
c68516ca8d | ||
|
|
a9e739e08d | ||
|
|
cc15ff73b7 | ||
|
|
fd3a4536e7 | ||
|
|
75caafb5ac | ||
|
|
cf2f8165eb | ||
|
|
a2f80740d7 | ||
|
|
c7703791d1 | ||
|
|
c39e9e9c61 | ||
|
|
713059319c | ||
|
|
d7a7509da6 | ||
|
|
57e6e7b2c5 | ||
|
|
d675977f10 | ||
|
|
7ef911e17d | ||
|
|
9a384c5045 | ||
|
|
2b3753d99c | ||
|
|
d173ab9f45 | ||
|
|
fbd0e8e26a | ||
|
|
50414b333a | ||
|
|
ee2fdb3d25 | ||
|
|
2b884377d4 |
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -23,7 +23,7 @@ A clear and concise description of what you expected to happen.
|
|||||||
**Screenshots**
|
**Screenshots**
|
||||||
If applicable, add screenshots to help explain your problem.
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
**Enviroment details**
|
**Environment details**
|
||||||
OS: Name and version of operating system(s)
|
OS: Name and version of operating system(s)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
13
.github/workflows/ci.py
vendored
13
.github/workflows/ci.py
vendored
@@ -5,6 +5,7 @@
|
|||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import shlex
|
import shlex
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tarfile
|
import tarfile
|
||||||
@@ -35,7 +36,7 @@ def install_deps():
|
|||||||
run('sudo apt-get update')
|
run('sudo apt-get update')
|
||||||
run('sudo apt-get install -y libgl1-mesa-dev libxi-dev libxrandr-dev libxinerama-dev'
|
run('sudo apt-get install -y libgl1-mesa-dev libxi-dev libxrandr-dev libxinerama-dev'
|
||||||
' libxcursor-dev libxcb-xkb-dev libdbus-1-dev libxkbcommon-dev libharfbuzz-dev'
|
' libxcursor-dev libxcb-xkb-dev libdbus-1-dev libxkbcommon-dev libharfbuzz-dev'
|
||||||
' libpng-dev libfontconfig-dev libxkbcommon-x11-dev libcanberra-dev')
|
' libpng-dev liblcms2-dev libfontconfig-dev libxkbcommon-x11-dev libcanberra-dev')
|
||||||
if is_bundle:
|
if is_bundle:
|
||||||
install_bundle()
|
install_bundle()
|
||||||
else:
|
else:
|
||||||
@@ -43,7 +44,7 @@ def install_deps():
|
|||||||
|
|
||||||
|
|
||||||
def build_kitty():
|
def build_kitty():
|
||||||
python = 'python3' if is_bundle else sys.executable
|
python = shutil.which('python3') if is_bundle else sys.executable
|
||||||
cmd = '{} setup.py build --verbose'.format(python)
|
cmd = '{} setup.py build --verbose'.format(python)
|
||||||
if os.environ.get('KITTY_SANITIZE') == '1':
|
if os.environ.get('KITTY_SANITIZE') == '1':
|
||||||
cmd += ' --debug --sanitize'
|
cmd += ' --debug --sanitize'
|
||||||
@@ -71,7 +72,7 @@ def replace_in_file(path, src, dest):
|
|||||||
|
|
||||||
def setup_bundle_env():
|
def setup_bundle_env():
|
||||||
global SW
|
global SW
|
||||||
os.environ['SW'] = SW = '/Users/Shared/buildbot/sw/sw' if is_macos else os.path.join(os.environ['GITHUB_WORKSPACE'], 'sw')
|
os.environ['SW'] = SW = '/Users/Shared/kitty-build/sw/sw' if is_macos else os.path.join(os.environ['GITHUB_WORKSPACE'], 'sw')
|
||||||
os.environ['PKG_CONFIG_PATH'] = SW + '/lib/pkgconfig'
|
os.environ['PKG_CONFIG_PATH'] = SW + '/lib/pkgconfig'
|
||||||
if is_macos:
|
if is_macos:
|
||||||
os.environ['PATH'] = '{}:{}'.format('/usr/local/opt/sphinx-doc/bin', os.environ['PATH'])
|
os.environ['PATH'] = '{}:{}'.format('/usr/local/opt/sphinx-doc/bin', os.environ['PATH'])
|
||||||
@@ -85,8 +86,8 @@ def install_bundle():
|
|||||||
cwd = os.getcwd()
|
cwd = os.getcwd()
|
||||||
os.makedirs(SW)
|
os.makedirs(SW)
|
||||||
os.chdir(SW)
|
os.chdir(SW)
|
||||||
with urlopen('https://download.calibre-ebook.com/travis/kitty/{}.tar.xz'.format(
|
with urlopen('https://download.calibre-ebook.com/ci/kitty/{}-64.tar.xz'.format(
|
||||||
'osx' if is_macos else 'linux-64')) as f:
|
'macos' if is_macos else 'linux')) as f:
|
||||||
data = f.read()
|
data = f.read()
|
||||||
with tarfile.open(fileobj=io.BytesIO(data), mode='r:xz') as tf:
|
with tarfile.open(fileobj=io.BytesIO(data), mode='r:xz') as tf:
|
||||||
tf.extractall()
|
tf.extractall()
|
||||||
@@ -106,7 +107,7 @@ def main():
|
|||||||
if is_bundle:
|
if is_bundle:
|
||||||
setup_bundle_env()
|
setup_bundle_env()
|
||||||
else:
|
else:
|
||||||
if not is_macos:
|
if not is_macos and 'pythonLocation' in os.environ:
|
||||||
os.environ['LD_LIBRARY_PATH'] = '{}/lib'.format(os.environ['pythonLocation'])
|
os.environ['LD_LIBRARY_PATH'] = '{}/lib'.format(os.environ['pythonLocation'])
|
||||||
action = sys.argv[-1]
|
action = sys.argv[-1]
|
||||||
if action in ('build', 'package'):
|
if action in ('build', 'package'):
|
||||||
|
|||||||
41
.github/workflows/codeql-analysis.yml
vendored
Normal file
41
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
name: "Code scanning - action"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master, ]
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [master]
|
||||||
|
schedule:
|
||||||
|
- cron: '0 22 * * 5'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
CodeQL-Build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
# We must fetch at least the immediate parents so that if this is
|
||||||
|
# a pull request then we can checkout the head.
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
# If this run was triggered by a pull request event, then checkout
|
||||||
|
# the head of the pull request instead of the merge commit.
|
||||||
|
- run: git checkout HEAD^2
|
||||||
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v1
|
||||||
|
# Override language selection by uncommenting this and choosing your languages
|
||||||
|
# with:
|
||||||
|
# languages: go, javascript, csharp, python, cpp, java
|
||||||
|
|
||||||
|
- name: Build kitty
|
||||||
|
run: python3 .github/workflows/ci.py build
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v1
|
||||||
@@ -7,6 +7,7 @@ import re
|
|||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
from contextlib import suppress
|
||||||
|
|
||||||
from bypy.constants import (
|
from bypy.constants import (
|
||||||
LIBDIR, PREFIX, PYTHON, SRC as KITTY_DIR, ismacos, worker_env
|
LIBDIR, PREFIX, PYTHON, SRC as KITTY_DIR, ismacos, worker_env
|
||||||
@@ -47,6 +48,16 @@ def build_c_extensions(ext_dir, args):
|
|||||||
shutil.copytree(
|
shutil.copytree(
|
||||||
KITTY_DIR, writeable_src_dir, symlinks=True,
|
KITTY_DIR, writeable_src_dir, symlinks=True,
|
||||||
ignore=shutil.ignore_patterns('b', 'build', 'dist', '*_commands.json', '*.o'))
|
ignore=shutil.ignore_patterns('b', 'build', 'dist', '*_commands.json', '*.o'))
|
||||||
|
|
||||||
|
# Build the launcher as it is needed for the spawn test
|
||||||
|
with suppress(FileNotFoundError):
|
||||||
|
os.remove(os.path.join(writeable_src_dir, 'kitty', 'launcher', 'kitty'))
|
||||||
|
if run(PYTHON, 'setup.py', 'build-launcher', cwd=writeable_src_dir) != 0:
|
||||||
|
print('Building of kitty launcher failed', file=sys.stderr)
|
||||||
|
os.chdir(KITTY_DIR)
|
||||||
|
run_shell()
|
||||||
|
raise SystemExit('Building of kitty launcher failed')
|
||||||
|
|
||||||
cmd = [PYTHON, 'setup.py']
|
cmd = [PYTHON, 'setup.py']
|
||||||
bundle = 'macos-freeze' if ismacos else 'linux-freeze'
|
bundle = 'macos-freeze' if ismacos else 'linux-freeze'
|
||||||
cmd.append(bundle)
|
cmd.append(bundle)
|
||||||
@@ -62,7 +73,12 @@ def build_c_extensions(ext_dir, args):
|
|||||||
|
|
||||||
|
|
||||||
def run_tests(path_to_kitty, cwd_on_failure):
|
def run_tests(path_to_kitty, cwd_on_failure):
|
||||||
ret = run(PYTHON, 'test.py', cwd=cwd_on_failure)
|
kw = {'cwd': cwd_on_failure}
|
||||||
|
if not ismacos:
|
||||||
|
# this is needed for the spawn test which starts an interpreter
|
||||||
|
# using the kitty launcher.
|
||||||
|
kw['PYTHONHOME'] = PREFIX
|
||||||
|
ret = run(PYTHON, 'test.py', **kw)
|
||||||
if ret != 0:
|
if ret != 0:
|
||||||
os.chdir(cwd_on_failure)
|
os.chdir(cwd_on_failure)
|
||||||
print(
|
print(
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ kitty_constants = iv['kitty_constants']
|
|||||||
|
|
||||||
def binary_includes():
|
def binary_includes():
|
||||||
return tuple(map(get_dll_path, (
|
return tuple(map(get_dll_path, (
|
||||||
'expat', 'sqlite3', 'ffi', 'z', 'lzma', 'png16',
|
'expat', 'sqlite3', 'ffi', 'z', 'lzma', 'png16', 'lcms2',
|
||||||
|
|
||||||
# dont include freetype because fontconfig is closely coupled to it
|
# dont include freetype because fontconfig is closely coupled to it
|
||||||
# and also distros often patch freetype
|
# and also distros often patch freetype
|
||||||
@@ -35,7 +35,7 @@ def binary_includes():
|
|||||||
'harfbuzz', 'xkbcommon', 'xkbcommon-x11',
|
'harfbuzz', 'xkbcommon', 'xkbcommon-x11',
|
||||||
'ncursesw', 'readline',
|
'ncursesw', 'readline',
|
||||||
))) + (
|
))) + (
|
||||||
get_dll_path('bz2', 2), get_dll_path('ssl', 3), get_dll_path('crypto', 3),
|
get_dll_path('bz2', 2), get_dll_path('ssl', 2), get_dll_path('crypto', 2),
|
||||||
get_dll_path('python' + py_ver, 2),
|
get_dll_path('python' + py_ver, 2),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -165,7 +165,7 @@ def create_tarfile(env, compression_level='9'):
|
|||||||
base = OUTPUT_DIR
|
base = OUTPUT_DIR
|
||||||
try:
|
try:
|
||||||
shutil.rmtree(base)
|
shutil.rmtree(base)
|
||||||
except EnvironmentError as err:
|
except OSError as err:
|
||||||
if err.errno != errno.ENOENT:
|
if err.errno != errno.ENOENT:
|
||||||
raise
|
raise
|
||||||
os.mkdir(base)
|
os.mkdir(base)
|
||||||
|
|||||||
@@ -262,8 +262,9 @@ class Freeze(object):
|
|||||||
'z.1',
|
'z.1',
|
||||||
'harfbuzz.0',
|
'harfbuzz.0',
|
||||||
'png16.16',
|
'png16.16',
|
||||||
'crypto.1.0.0',
|
'lcms2.2',
|
||||||
'ssl.1.0.0',
|
'crypto.1.1',
|
||||||
|
'ssl.1.1',
|
||||||
):
|
):
|
||||||
print('\nAdding', x)
|
print('\nAdding', x)
|
||||||
x = 'lib%s.dylib' % x
|
x = 'lib%s.dylib' % x
|
||||||
|
|||||||
@@ -71,8 +71,8 @@
|
|||||||
{
|
{
|
||||||
"name": "openssl",
|
"name": "openssl",
|
||||||
"unix": {
|
"unix": {
|
||||||
"filename": "openssl-1.0.2u.tar.gz",
|
"filename": "openssl-1.1.1g.tar.gz",
|
||||||
"hash": "sha256:ecd0c6ffb493dd06707d38b14bb4d8c2288bb7033735606569d8f90f89669d16",
|
"hash": "sha256:ddb04774f1e32f0c49751e21b67216ac87852ceb056b75209af2443400636d46",
|
||||||
"urls": ["https://www.openssl.org/source/{filename}"]
|
"urls": ["https://www.openssl.org/source/{filename}"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -153,6 +153,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "lcms2",
|
||||||
|
"unix": {
|
||||||
|
"filename": "lcms2-2.11.tar.gz",
|
||||||
|
"hash": "sha256:478c9c3938d7a91b1171de4616f8b04308a8676d73eadc19505b7ace41327f28",
|
||||||
|
"urls": ["https://github.com/mm2/Little-CMS/archive/2.11/{filename}"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "graphite",
|
"name": "graphite",
|
||||||
"os": "linux",
|
"os": "linux",
|
||||||
@@ -179,7 +188,7 @@
|
|||||||
"unix": {
|
"unix": {
|
||||||
"filename": "libiconv-1.16.tar.gz",
|
"filename": "libiconv-1.16.tar.gz",
|
||||||
"hash": "sha256:e6a1b1b589654277ee790cce3734f07876ac4ccfaecbee8afa0b649cf529cc04",
|
"hash": "sha256:e6a1b1b589654277ee790cce3734f07876ac4ccfaecbee8afa0b649cf529cc04",
|
||||||
"urls": ["http://ftp.gnu.org/pub/gnu/libiconv/{filename}"]
|
"urls": ["https://ftp.gnu.org/pub/gnu/libiconv/{filename}"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -187,13 +196,12 @@
|
|||||||
"name": "glib",
|
"name": "glib",
|
||||||
"os": "linux",
|
"os": "linux",
|
||||||
"unix": {
|
"unix": {
|
||||||
"filename": "glib-2.57.1.tar.xz",
|
"filename": "glib-2.65.1.tar.xz",
|
||||||
"hash": "sha256:d029e7c4536835f1f103472f7510332c28d58b9b7d6cd0e9f45c2653e670d9b4",
|
"hash": "sha256:bc63bf6c32713e0ee1dddc28e03f23b4a20c78bcb9a2c5b0f4eea41e46fb9cee",
|
||||||
"urls": ["http://ftp.gnome.org/pub/GNOME/sources/glib/2.57/{filename}"]
|
"urls": ["https://ftp.gnome.org/pub/GNOME/sources/glib/2.65/{filename}"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "freetype",
|
"name": "freetype",
|
||||||
"os": "linux",
|
"os": "linux",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ Run-time dependencies:
|
|||||||
* harfbuzz >= 1.5.0
|
* harfbuzz >= 1.5.0
|
||||||
* zlib
|
* zlib
|
||||||
* libpng
|
* libpng
|
||||||
|
* liblcms2
|
||||||
* freetype (not needed on macOS)
|
* freetype (not needed on macOS)
|
||||||
* fontconfig (not needed on macOS)
|
* fontconfig (not needed on macOS)
|
||||||
* libcanberra (not needed on macOS)
|
* libcanberra (not needed on macOS)
|
||||||
@@ -63,10 +64,19 @@ This :file:`kitty.app` unlike the released one does not include its own copy of
|
|||||||
python and the other dependencies. So if you ever un-install/upgrade those dependencies
|
python and the other dependencies. So if you ever un-install/upgrade those dependencies
|
||||||
you might have to rebuild the app.
|
you might have to rebuild the app.
|
||||||
|
|
||||||
Note that the released :file:`kitty.dmg` includes all dependencies, unlike the
|
.. note::
|
||||||
:file:`kitty.app` built above and is built automatically by using the :file:`kitty` branch of
|
The released :file:`kitty.dmg` includes all dependencies, unlike the
|
||||||
`build-calibre <https://github.com/kovidgoyal/build-calibre>`_ however, that
|
:file:`kitty.app` built above and is built automatically by using the
|
||||||
is designed to run on Linux and is not for the faint of heart.
|
:file:`kitty` branch of `build-calibre
|
||||||
|
<https://github.com/kovidgoyal/build-calibre>`_ however, that is designed to
|
||||||
|
run on Linux and is not for the faint of heart.
|
||||||
|
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Apple disallows certain functionality, such as notifications for unsigned applications.
|
||||||
|
If you need this functionality, you can try signing the built kitty.app with
|
||||||
|
a self signed certificate, see for example, `here
|
||||||
|
<https://stackoverflow.com/questions/27474751/how-can-i-codesign-an-app-without-being-in-the-mac-developer-program/27474942>`_.
|
||||||
|
|
||||||
|
|
||||||
Note for Linux/macOS packagers
|
Note for Linux/macOS packagers
|
||||||
|
|||||||
@@ -4,6 +4,164 @@ Changelog
|
|||||||
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
||||||
To update |kitty|, :doc:`follow the instructions <binary>`.
|
To update |kitty|, :doc:`follow the instructions <binary>`.
|
||||||
|
|
||||||
|
0.19.0 [2020-10-04]
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- Add support for `hyperlinks from terminal programs
|
||||||
|
<https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda>`_.
|
||||||
|
Controlled via :opt:`allow_hyperlinks` (:iss:`68`)
|
||||||
|
|
||||||
|
- Add support for easily editing or downloading files over SSH sessions
|
||||||
|
without the need for any special software, see :doc:`kittens/remote_file`
|
||||||
|
|
||||||
|
- A new :doc:`kittens/hyperlinked_grep` kitten to easily search files and open
|
||||||
|
the results at the matched line by clicking on them.
|
||||||
|
|
||||||
|
- Allow customizing the :doc:`actions kitty takes <open_actions>` when clicking on URLs
|
||||||
|
|
||||||
|
- Improve rendering of borders when using minimal borders. Use less space and
|
||||||
|
do not display a box around active windows
|
||||||
|
|
||||||
|
- Add a new extensible escape code to allow terminal programs to trigger
|
||||||
|
desktop notifications. See :ref:`desktop_notifications` (:iss:`1474`)
|
||||||
|
|
||||||
|
- Implement special rendering for various characters from the set of "Symbols
|
||||||
|
for Legacy Computing" from the Unicode 13 standard
|
||||||
|
|
||||||
|
- Unicode input kitten: Allow choosing symbols from the NERD font as well.
|
||||||
|
These are mostly Private Use symbols not in any standard, however are common. (:iss:`2972`)
|
||||||
|
|
||||||
|
- Allow specifying border sizes in either pts or pixels. Change the default to
|
||||||
|
0.5pt borders as this works best with the new minimal border style
|
||||||
|
|
||||||
|
- Add support for displaying correct colors with non-sRGB PNG files (Adds a
|
||||||
|
dependency on liblcms2)
|
||||||
|
|
||||||
|
- hints kitten: Add a new :option:`kitty +kitten hints --type` of ``hyperlink`` useful
|
||||||
|
for activating hyperlinks using just the keyboard
|
||||||
|
|
||||||
|
- Allow tracking focus change events in watchers (:iss:`2918`)
|
||||||
|
|
||||||
|
- Allow specifying watchers in session files and via a command line argument
|
||||||
|
(:iss:`2933`)
|
||||||
|
|
||||||
|
- Add a setting :opt:`tab_activity_symbol` to show a symbol in the tab title
|
||||||
|
if one of the windows has some activity after it was last focused
|
||||||
|
(:iss:`2515`)
|
||||||
|
|
||||||
|
- macOS: Switch to using the User Notifications framework for notifications.
|
||||||
|
The current notifications framework has been deprecated in Big Sur. The new
|
||||||
|
framework only allows notifications from signed and notarized applications,
|
||||||
|
so people using kitty from homebrew/source are out of luck. Complain to
|
||||||
|
Apple.
|
||||||
|
|
||||||
|
- When in the main screen and a program grabs the mouse, do not use the scroll
|
||||||
|
wheel events to scroll the scrollback buffer, instead send them to the
|
||||||
|
program (:iss:`2939`)
|
||||||
|
|
||||||
|
- Fix unfocused windows in which a bell occurs not changing their border color
|
||||||
|
to red until a relayout
|
||||||
|
|
||||||
|
- Linux: Fix automatic detection of bold/italic faces for fonts such as IBM
|
||||||
|
Plex Mono that have the regular face with a full name that is the same as the
|
||||||
|
family name (:iss:`2951`)
|
||||||
|
|
||||||
|
- Fix a regression that broke :opt:`kitten_alias` (:iss:`2952`)
|
||||||
|
|
||||||
|
- Fix a regression that broke the ``move_window_to_top`` action (:pull:`2953`)
|
||||||
|
|
||||||
|
- Fix a memory leak when changing font sizes
|
||||||
|
|
||||||
|
- Fix some lines in the scrollback buffer not being properly rendered after a
|
||||||
|
window resize/font size change (:iss:`2619`)
|
||||||
|
|
||||||
|
|
||||||
|
0.18.3 [2020-08-11]
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- hints kitten: Allow customizing hint colors (:pull:`2894`)
|
||||||
|
|
||||||
|
- Wayland: Fix a typo in the previous release that broke reading mouse cursor size (:iss:`2895`)
|
||||||
|
|
||||||
|
- Fix a regression in the previous release that could cause an exception during
|
||||||
|
startup in rare circumstances (:iss:`2896`)
|
||||||
|
|
||||||
|
- Fix image leaving behind a black rectangle when switch away and back to
|
||||||
|
alternate screen (:iss:`2901`)
|
||||||
|
|
||||||
|
- Fix one pixel mis-alignment of rounded corners when either the cell
|
||||||
|
dimensions or the thickness of the line is an odd number of pixels
|
||||||
|
(:iss:`2907`)
|
||||||
|
|
||||||
|
- Fix a regression that broke specifying OS window size in the session file
|
||||||
|
(:iss:`2908`)
|
||||||
|
|
||||||
|
|
||||||
|
0.18.2 [2020-07-28]
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
- X11: Improve handling of multiple keyboards. Now pressing a modifier key in
|
||||||
|
one keyboard and a normal key in another works (:iss:`2362`). Don't rebuild
|
||||||
|
keymaps on new keyboard events that only change geometry (:iss:`2787`).
|
||||||
|
Better handling of multiple keyboards with incompatible layouts (:iss:`2726`)
|
||||||
|
|
||||||
|
- Improve anti-aliasing of triangular box drawing characters, noticeable on
|
||||||
|
low-resolution screens (:iss:`2844`)
|
||||||
|
|
||||||
|
- Fix ``kitty @ send-text`` not working reliably when using a socket for remote
|
||||||
|
control (:iss:`2852`)
|
||||||
|
|
||||||
|
- Implement support for box drawing rounded-corners characters (:iss:`2240`)
|
||||||
|
|
||||||
|
- Allow setting the class for new OS windows in a session file
|
||||||
|
|
||||||
|
- When a character from the Unicode Dingbat block is followed by a space, use
|
||||||
|
the extra space to render a larger version of the character (:iss:`2850`)
|
||||||
|
|
||||||
|
- macOS: Fix the LC_CTYPE env var being set to UTF-8 on systems in which the
|
||||||
|
language and country code do not form a valid locale (:iss:`1233`)
|
||||||
|
|
||||||
|
- macOS: Fix :kbd:`cmd+plus` not changing font size (:iss:`2839`)
|
||||||
|
|
||||||
|
- Make neighboring window selection in grid and splits layouts more intelligent
|
||||||
|
(:pull:`2840`)
|
||||||
|
|
||||||
|
- Allow passing the current selection to kittens (:iss:`2796`)
|
||||||
|
|
||||||
|
- Fix pre-edit text not always being cleared with ibus input (:iss:`2862`)
|
||||||
|
|
||||||
|
- Allow setting the :opt:`background_opacity` of new OS windows created via
|
||||||
|
:option:`kitty --single-instance` using the :option:`kitty --override` command line
|
||||||
|
argument (:iss:`2806`)
|
||||||
|
|
||||||
|
- Fix the CSI J (Erase in display ED) escape code not removing line continued
|
||||||
|
markers (:iss:`2809`)
|
||||||
|
|
||||||
|
- hints kitten: In linenumber mode expand paths that starts with ~
|
||||||
|
(:iss:`2822`)
|
||||||
|
|
||||||
|
- Fix ``launch --location=last`` not working (:iss:`2841`)
|
||||||
|
|
||||||
|
- Fix incorrect centering when a PUA or symbol glyph is followed by more than one space
|
||||||
|
|
||||||
|
- Have the :opt:`confirm_os_window_close` option also apply when closing tabs
|
||||||
|
with multiple windows (:iss:`2857`)
|
||||||
|
|
||||||
|
- Add support for legacy DECSET codes 47, 1047 and 1048 (:pull:`2871`)
|
||||||
|
|
||||||
|
- macOS: no longer render emoji 20% below the baseline. This caused some emoji
|
||||||
|
to be cut-off and also look misaligned with very high cells (:iss:`2873`)
|
||||||
|
|
||||||
|
- macOS: Make the window id of OS windows available in the ``WINDOWID``
|
||||||
|
environment variable (:pull:`2877`)
|
||||||
|
|
||||||
|
- Wayland: Fix a regression in 0.18.0 that could cause crashes related to mouse
|
||||||
|
cursors in some rare circumstances (:iss:`2810`)
|
||||||
|
|
||||||
|
- Fix change in window size that does not change number of cells not being
|
||||||
|
reported to the kernel (:iss:`2880`)
|
||||||
|
|
||||||
|
|
||||||
0.18.1 [2020-06-23]
|
0.18.1 [2020-06-23]
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
|||||||
@@ -555,7 +555,7 @@ def process_shortcut_link(env: Any, refnode: Any, has_explicit_title: bool, titl
|
|||||||
|
|
||||||
|
|
||||||
def write_conf_docs(app: Any, all_kitten_names: Iterable[str]) -> None:
|
def write_conf_docs(app: Any, all_kitten_names: Iterable[str]) -> None:
|
||||||
app.add_lexer('conf', ConfLexer())
|
app.add_lexer('conf', ConfLexer)
|
||||||
app.add_object_type(
|
app.add_object_type(
|
||||||
'opt', 'opt',
|
'opt', 'opt',
|
||||||
indextemplate="pair: %s; Config Setting",
|
indextemplate="pair: %s; Config Setting",
|
||||||
@@ -604,7 +604,7 @@ def setup(app: Any) -> None:
|
|||||||
write_cli_docs(kn)
|
write_cli_docs(kn)
|
||||||
write_remote_control_protocol_docs()
|
write_remote_control_protocol_docs()
|
||||||
write_conf_docs(app, kn)
|
write_conf_docs(app, kn)
|
||||||
app.add_lexer('session', SessionLexer())
|
app.add_lexer('session', SessionLexer)
|
||||||
app.add_role('link', link_role)
|
app.add_role('link', link_role)
|
||||||
app.add_role('iss', partial(num_role, 'issues'))
|
app.add_role('iss', partial(num_role, 'issues'))
|
||||||
app.add_role('pull', partial(num_role, 'pull'))
|
app.add_role('pull', partial(num_role, 'pull'))
|
||||||
|
|||||||
27
docs/faq.rst
27
docs/faq.rst
@@ -232,3 +232,30 @@ available. The manual way to figure it out is:
|
|||||||
This shows the hex value, ``13`` in this case.
|
This shows the hex value, ``13`` in this case.
|
||||||
|
|
||||||
3. Use ``\x(hexval)`` in your ``send_text`` command in kitty. So in this example, ``\x13``
|
3. Use ``\x(hexval)`` in your ``send_text`` command in kitty. So in this example, ``\x13``
|
||||||
|
|
||||||
|
|
||||||
|
I am using tmux and have a problem
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
First, terminal multiplexers are `a bad idea
|
||||||
|
<https://github.com/kovidgoyal/kitty/issues/391#issuecomment-638320745>`_, do
|
||||||
|
not use them, if at all possible. kitty contains features that do all of what
|
||||||
|
tmux does, but better, with the exception of remote persistence (:iss:`391`).
|
||||||
|
If you still want to use tmux, read on.
|
||||||
|
|
||||||
|
Image display will not work, see `tmux issue
|
||||||
|
<https://github.com/tmux/tmux/issues/1391>`_.
|
||||||
|
|
||||||
|
If you are using tmux with multiple terminals or you start it under one
|
||||||
|
terminal and then switch to another and these terminals have different TERM
|
||||||
|
variables, tmux will break. You will need to restart it as tmux does not
|
||||||
|
support multiple terminfo definitions.
|
||||||
|
|
||||||
|
Copying to clipboard via OSC 52 will not work, because tmux does not support
|
||||||
|
the extended version of that protocol, you will need to add ``no-append`` to
|
||||||
|
:opt:`clipboard_control` in kitty.conf.
|
||||||
|
|
||||||
|
If you use any of the advanced features that kitty has innovated, such as
|
||||||
|
styled underlines, desktop notifications, extended keyboard support, etc.
|
||||||
|
they may or may not work, depending on the whims of tmux's maintainer, your
|
||||||
|
version of tmux, etc.
|
||||||
|
|||||||
@@ -54,27 +54,34 @@ In C:
|
|||||||
|
|
||||||
.. code-block:: c
|
.. code-block:: c
|
||||||
|
|
||||||
struct ttysize ts;
|
#include <stdio.h>
|
||||||
ioctl(0, TIOCGWINSZ, &ts);
|
#include <sys/ioctl.h>
|
||||||
printf("number of columns: %i, number of rows: %i, screen width: %i, screen height: %i\n", sz.ws_col, sz.ws_row, sz.ws_xpixel, sz.ws_ypixel);
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
struct winsize sz;
|
||||||
|
ioctl(0, TIOCGWINSZ, &sz);
|
||||||
|
printf("number of rows: %i, number of columns: %i, screen width: %i, screen height: %i\n", sz.ws_row, sz.ws_col, sz.ws_xpixel, sz.ws_ypixel);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
In Python:
|
In Python:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
import array, fcntl, termios
|
import array, fcntl, sys, termios
|
||||||
buf = array.array('H', [0, 0, 0, 0])
|
buf = array.array('H', [0, 0, 0, 0])
|
||||||
fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ, buf)
|
fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ, buf)
|
||||||
print('number of columns: {}, number of rows: {}, screen width: {}, screen height: {}'.format(*buf))
|
print('number of rows: {}, number of columns: {}, screen width: {}, screen height: {}'.format(*buf))
|
||||||
|
|
||||||
Note that some terminals return ``0`` for the width and height values. Such
|
Note that some terminals return ``0`` for the width and height values. Such
|
||||||
terminals should be modified to return the correct values. Examples of
|
terminals should be modified to return the correct values. Examples of
|
||||||
terminals that return correct values: ``kitty, xterm``
|
terminals that return correct values: ``kitty, xterm``
|
||||||
|
|
||||||
You can also use the *CSI t* escape code to get the screen size. Send
|
You can also use the *CSI t* escape code to get the screen size. Send
|
||||||
``<ESC>[14t`` to *stdout* and kitty will reply on *stdin* with
|
``<ESC>[14t`` to ``STDOUT`` and kitty will reply on ``STDIN`` with
|
||||||
``<ESC>[4;<height>;<width>t`` where *height* and *width* are the window size in
|
``<ESC>[4;<height>;<width>t`` where ``height`` and ``width`` are the window
|
||||||
pixels. This escape code is supported in many terminals, not just kitty.
|
size in pixels. This escape code is supported in many terminals, not just
|
||||||
|
kitty.
|
||||||
|
|
||||||
A minimal example
|
A minimal example
|
||||||
------------------
|
------------------
|
||||||
@@ -154,9 +161,10 @@ of transmitting paletted images.
|
|||||||
RGB and RGBA data
|
RGB and RGBA data
|
||||||
~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
In these formats the pixel data is stored directly as 3 or 4 bytes per pixel, respectively.
|
In these formats the pixel data is stored directly as 3 or 4 bytes per pixel,
|
||||||
When specifying images in this format, the image dimensions **must** be sent in the control data.
|
respectively. The colors in the data **must** be in the *sRGB color space*. When
|
||||||
For example::
|
specifying images in this format, the image dimensions **must** be sent in the
|
||||||
|
control data. For example::
|
||||||
|
|
||||||
<ESC>_Gf=24,s=10,v=20;<payload><ESC>\
|
<ESC>_Gf=24,s=10,v=20;<payload><ESC>\
|
||||||
|
|
||||||
|
|||||||
@@ -10,10 +10,12 @@ kitty - the fast, featureful, GPU based terminal emulator
|
|||||||
buttery smooth scrolling. Uses threaded rendering to minimize input latency.
|
buttery smooth scrolling. Uses threaded rendering to minimize input latency.
|
||||||
|
|
||||||
* Supports all modern terminal features: :doc:`graphics (images)
|
* Supports all modern terminal features: :doc:`graphics (images)
|
||||||
<graphics-protocol>`, unicode, true-color, OpenType ligatures, mouse
|
<graphics-protocol>`, unicode, `true-color
|
||||||
protocol, focus tracking, `bracketed paste
|
<https://gist.github.com/XVilka/8346728>`_,
|
||||||
<https://cirw.in/blog/bracketed-paste>`_ and several :doc:`new terminal
|
OpenType ligatures, mouse protocol, :doc:`hyperlinks <open_actions>`,
|
||||||
protocol extensions <protocol-extensions>`.
|
focus tracking, `bracketed paste <https://cirw.in/blog/bracketed-paste>`_
|
||||||
|
and several :doc:`new terminal protocol extensions
|
||||||
|
<protocol-extensions>`.
|
||||||
|
|
||||||
* Supports tiling multiple terminal windows side by side in different
|
* Supports tiling multiple terminal windows side by side in different
|
||||||
:ref:`layouts <layouts>` without needing to use an extra program like tmux
|
:ref:`layouts <layouts>` without needing to use an extra program like tmux
|
||||||
@@ -346,11 +348,22 @@ For example:
|
|||||||
new_os_window
|
new_os_window
|
||||||
# set new window size to 80x25 cells
|
# set new window size to 80x25 cells
|
||||||
os_window_size 80c 25c
|
os_window_size 80c 25c
|
||||||
|
# set the --class for the new OS window
|
||||||
|
os_window_class mywindow
|
||||||
launch sh
|
launch sh
|
||||||
# Make the current window the active (focused) window
|
# Make the current window the active (focused) window
|
||||||
focus
|
focus
|
||||||
launch emacs
|
launch emacs
|
||||||
|
|
||||||
|
# Add a watcher that will be called with various events that occur
|
||||||
|
# on all subsequent windows. See the documentation of the launch command
|
||||||
|
# for details on watchers.
|
||||||
|
watcher /some/python/file.py
|
||||||
|
launch mpd
|
||||||
|
launch irssi
|
||||||
|
# Remove the watcher for further windows
|
||||||
|
watcher clear
|
||||||
|
|
||||||
|
|
||||||
Mouse features
|
Mouse features
|
||||||
-------------------
|
-------------------
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ images and other types of documents directly in your terminal, even over SSH.
|
|||||||
`nnn <https://github.com/jarun/nnn/>`_
|
`nnn <https://github.com/jarun/nnn/>`_
|
||||||
another terminal file manager, with previews of file contents powered by kitty's graphics protocol.
|
another terminal file manager, with previews of file contents powered by kitty's graphics protocol.
|
||||||
|
|
||||||
|
`hunter <https://github.com/rabite0/hunter>`_
|
||||||
|
another terminal file manager, with previews of file contents powered by kitty's graphics protocol.
|
||||||
|
|
||||||
`koneko <https://github.com/twenty5151/koneko>`_
|
`koneko <https://github.com/twenty5151/koneko>`_
|
||||||
browse images from the pixiv artist community directly in kitty
|
browse images from the pixiv artist community directly in kitty
|
||||||
|
|
||||||
@@ -106,3 +109,10 @@ Miscellaneous
|
|||||||
`reload keybindings <https://github.com/kovidgoyal/kitty/issues/1292#issuecomment-582388769>`_
|
`reload keybindings <https://github.com/kovidgoyal/kitty/issues/1292#issuecomment-582388769>`_
|
||||||
reload key bindings from :file:`kitty.conf` without needing to restart
|
reload key bindings from :file:`kitty.conf` without needing to restart
|
||||||
kitty
|
kitty
|
||||||
|
|
||||||
|
`kitti3 <https://github.com/LandingEllipse/kitti3>`_
|
||||||
|
allow using kitty as a drop-down terminal under the i3 window manager
|
||||||
|
|
||||||
|
`weechat-hints <https://github.com/GermainZ/kitty-weechat-hints>`_
|
||||||
|
URL hints kitten for WeeChat that works without having to use WeeChat's
|
||||||
|
raw-mode.
|
||||||
|
|||||||
@@ -58,7 +58,8 @@ You can pass arguments to kittens by defining them in the map directive in
|
|||||||
These will be available as the ``args`` parameter in the ``main()`` and
|
These will be available as the ``args`` parameter in the ``main()`` and
|
||||||
``handle_result()`` functions. Note also that the current working directory
|
``handle_result()`` functions. Note also that the current working directory
|
||||||
of the kitten is set to the working directory of whatever program is
|
of the kitten is set to the working directory of whatever program is
|
||||||
running in the active kitty window.
|
running in the active kitty window. The special argument ``@selection``
|
||||||
|
is replaced by the currently selected text in the active kitty window.
|
||||||
|
|
||||||
|
|
||||||
Passing the contents of the screen to the kitten
|
Passing the contents of the screen to the kitten
|
||||||
@@ -87,7 +88,8 @@ This will send the plain text of the active window to the kitten's
|
|||||||
:file:`STDIN`. For text with formatting escape codes, use ``ansi``
|
:file:`STDIN`. For text with formatting escape codes, use ``ansi``
|
||||||
instead. If you want line wrap markers as well, use ``screen-ansi``
|
instead. If you want line wrap markers as well, use ``screen-ansi``
|
||||||
or just ``screen``. For the scrollback buffer as well, use
|
or just ``screen``. For the scrollback buffer as well, use
|
||||||
``history``, ``ansi-history`` or ``screen-history``.
|
``history``, ``ansi-history`` or ``screen-history``. To get
|
||||||
|
the currently selected text, use ``selection``.
|
||||||
|
|
||||||
|
|
||||||
Using kittens to script kitty, without any terminal UI
|
Using kittens to script kitty, without any terminal UI
|
||||||
@@ -154,8 +156,8 @@ from ``kitty.fast_data_types``.
|
|||||||
|
|
||||||
For example, to send a left click at position x: 2, y: 3 to the active window::
|
For example, to send a left click at position x: 2, y: 3 to the active window::
|
||||||
|
|
||||||
from kitty.fast_data_types import send_mouse_event, GLFW_MOUSE_BUTTON_LEFT, PRESS
|
from kitty.fast_data_types import send_mouse_event, PRESS
|
||||||
send_mouse_event(boss.active_window.screen, 2, 3, GLFW_MOUSE_BUTTON_LEFT, PRESS, 0)
|
send_mouse_event(boss.active_window.screen, 2, 3, 1, PRESS, 0)
|
||||||
|
|
||||||
The function will only send the event if the program is receiving events of
|
The function will only send the event if the program is receiving events of
|
||||||
that type, and will return ``True`` if it sent the event, and ``False`` if not.
|
that type, and will return ``True`` if it sent the event, and ``False`` if not.
|
||||||
@@ -194,3 +196,7 @@ Kittens created by kitty users
|
|||||||
`insert password <https://github.com/kovidgoyal/kitty/issues/1222>`_
|
`insert password <https://github.com/kovidgoyal/kitty/issues/1222>`_
|
||||||
Insert a password from a CLI password manager, taking care to only do it at
|
Insert a password from a CLI password manager, taking care to only do it at
|
||||||
a password prompt.
|
a password prompt.
|
||||||
|
|
||||||
|
`weechat-hints <https://github.com/GermainZ/kitty-weechat-hints>`_
|
||||||
|
URL hints kitten for WeeChat that works without having to use WeeChat's
|
||||||
|
raw-mode.
|
||||||
|
|||||||
61
docs/kittens/hyperlinked_grep.rst
Normal file
61
docs/kittens/hyperlinked_grep.rst
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
Hyperlinked grep
|
||||||
|
=================
|
||||||
|
|
||||||
|
This kitten allows you to search your files using `ripgrep
|
||||||
|
<https://www.google.com/search?q=ripgrep>`_ and open the results
|
||||||
|
directly in your favorite editor in the terminal, at the line containing
|
||||||
|
the search result, simply by clicking on the result you want.
|
||||||
|
|
||||||
|
To set it up, first create :file:`~/.config/kitty/open-actions.conf` with the
|
||||||
|
following contents:
|
||||||
|
|
||||||
|
.. code:: conf
|
||||||
|
|
||||||
|
# Open any file with a fragment in vim, fragments are generated
|
||||||
|
# by the hyperlink_grep kitten and nothing else so far.
|
||||||
|
protocol file
|
||||||
|
fragment_matches [0-9]+
|
||||||
|
action launch --type=overlay vim +${FRAGMENT} ${FILE_PATH}
|
||||||
|
|
||||||
|
# Open text files without fragments in the editor
|
||||||
|
protocol file
|
||||||
|
mime text/*
|
||||||
|
action launch --type=overlay ${EDITOR} ${FILE_PATH}
|
||||||
|
|
||||||
|
|
||||||
|
Now, run a search with::
|
||||||
|
|
||||||
|
kitty +kitten hyperlinked_grep something
|
||||||
|
|
||||||
|
Hold down the :kbd:`ctrl+shift` keys and click on any of the
|
||||||
|
result lines, to open the file in vim at the matching line. If
|
||||||
|
you use some editor other than vim, you should adjust the
|
||||||
|
:file:`open-actions.conf` file accordingly.
|
||||||
|
|
||||||
|
Finally, add an alias to your shell's rc files to invoke the kitten as ``hg``::
|
||||||
|
|
||||||
|
alias hg='kitty +kitten hyperlink_grep'
|
||||||
|
|
||||||
|
|
||||||
|
You can now run searches with::
|
||||||
|
|
||||||
|
hg some-search-term
|
||||||
|
|
||||||
|
If you want to enable completion, for the kitten, you can delegate completion
|
||||||
|
to rg. For ZSH, you do that with::
|
||||||
|
|
||||||
|
compdef _rg kitty +kitten hyperlinked_grep
|
||||||
|
|
||||||
|
|
||||||
|
To learn more about kitty's powerful framework for customizing URL click
|
||||||
|
actions, :doc:`see here <../open_actions>`.
|
||||||
|
|
||||||
|
Hopefully, someday this functionality will make it into some `upstream grep
|
||||||
|
<https://github.com/BurntSushi/ripgrep/issues/665>`_
|
||||||
|
program directly removing the need for this kitten.
|
||||||
|
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
While you can pass any of ripgrep's comand line options to the kitten and
|
||||||
|
they will be forwarded to rg, do not use options that change the output
|
||||||
|
formatting as the kitten works by parsing the output from ripgrep.
|
||||||
35
docs/kittens/remote_file.rst
Normal file
35
docs/kittens/remote_file.rst
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
Remote files
|
||||||
|
==============
|
||||||
|
|
||||||
|
|kitty| has the ability to easily *Edit*, *Open* or *Download* files
|
||||||
|
from a computer into which you are SSHed. In your SSH session run::
|
||||||
|
|
||||||
|
ls --hyperlink=auto
|
||||||
|
|
||||||
|
Then hold down :kbd:`ctrl+shift` and click the name of the file.
|
||||||
|
|
||||||
|
.. figure:: ../screenshots/remote_file.png
|
||||||
|
:alt: Remote file actions
|
||||||
|
:align: center
|
||||||
|
:scale: 100%
|
||||||
|
|
||||||
|
Remote file actions
|
||||||
|
|
||||||
|
|kitty| will ask you what you want to do with the remote file. You can choose
|
||||||
|
to *Edit* it in which case kitty will download it and open it locally in your
|
||||||
|
``EDITOR``. As you make changes to the file, they are automatically transferred
|
||||||
|
to the remote computer. Note that this happens without needing to install *any*
|
||||||
|
special software on the server, beyond ``ls`` that supports hyperlinks.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Nested SSH sessions are not supported. The kitten will always try to copy
|
||||||
|
remote files from the first SSH host. This is because there is no way for
|
||||||
|
|kitty| to detect and follow a nested SSH session robustly.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
If you have not setup automatic password-less SSH access, then, when
|
||||||
|
editing starts you will be asked to enter your password just once,
|
||||||
|
thereafter the SSH connection will be re-used.
|
||||||
|
|
||||||
|
Similarly, you can choose to save the file to the local computer or download
|
||||||
|
and open it in its default file handler.
|
||||||
@@ -24,7 +24,7 @@ a leading period and the index for the match if you don't like to use arrow
|
|||||||
keys.
|
keys.
|
||||||
|
|
||||||
You can switch between modes using either the function keys or by pressing
|
You can switch between modes using either the function keys or by pressing
|
||||||
:kbd:`Ctrl+Tab` and :kbd:`Ctrl+Shift+Tab`.
|
:kbd:`Ctrl+[` and :kbd:`Ctrl+]`.
|
||||||
|
|
||||||
|
|
||||||
Command Line Interface
|
Command Line Interface
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Launching programs in new windows/tabs
|
|||||||
|
|
||||||
|
|
||||||
|kitty| has a :code:`launch` action that can be used to run arbitrary programs
|
|kitty| has a :code:`launch` action that can be used to run arbitrary programs
|
||||||
in news windows/tabs. It can be mapped to user defined shortcuts in kitty.conf.
|
in new windows/tabs. It can be mapped to user defined shortcuts in kitty.conf.
|
||||||
It is very powerful and allows sending the contents of
|
It is very powerful and allows sending the contents of
|
||||||
the current window to the launched program, as well as many other options.
|
the current window to the launched program, as well as many other options.
|
||||||
|
|
||||||
@@ -74,6 +74,9 @@ functions for the events you are interested in, for example:
|
|||||||
def on_resize(boss, window, data):
|
def on_resize(boss, window, data):
|
||||||
# Here data will contain old_geometry and new_geometry
|
# Here data will contain old_geometry and new_geometry
|
||||||
|
|
||||||
|
def on_focus_change(boss, window, data):
|
||||||
|
# Here data kill contain focused
|
||||||
|
|
||||||
def on_close(boss, window, data):
|
def on_close(boss, window, data):
|
||||||
# called when window is closed, typically when the program running in
|
# called when window is closed, typically when the program running in
|
||||||
# it exits.
|
# it exits.
|
||||||
|
|||||||
91
docs/open_actions.rst
Normal file
91
docs/open_actions.rst
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
Customizing the actions taken when clicking on links
|
||||||
|
======================================================
|
||||||
|
|
||||||
|
|kitty| has support for `terminal hyperlinks <https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda>`_. These
|
||||||
|
are generated by many terminal programs, such as ``ls``, ``gcc``, ``systemd``,
|
||||||
|
``mdcat``, etc. You can customize exactly what happens when clicking on these hyperlinks
|
||||||
|
in |kitty|.
|
||||||
|
|
||||||
|
You can tell kitty to take arbitrarily many, complex actions
|
||||||
|
when a link is clicked. Let us illustrate with some examples, first. Create
|
||||||
|
the file :file:`~/.config/kitty/open-actions.conf` with the following:
|
||||||
|
|
||||||
|
.. code:: conf
|
||||||
|
|
||||||
|
# Open any image in the full kitty window by clicking on it
|
||||||
|
protocol file
|
||||||
|
mime image/*
|
||||||
|
action launch --type=overlay kitty +kitten icat --hold ${FILE_PATH}
|
||||||
|
|
||||||
|
Now, run ``ls --hyperlink=auto`` in kitty and click on the filename of an
|
||||||
|
image, holding down :kbd:`ctrl+shift`. It will be opened over the current
|
||||||
|
window. Press any key to close it.
|
||||||
|
|
||||||
|
Each entry in :file:`open-actions.conf` consists of one or more
|
||||||
|
:ref:`matching_criteria`, such as ``protocol`` and ``mime`` and one or more
|
||||||
|
``action`` entries. In the example above kitty uses the :doc:`launch <launch>`
|
||||||
|
action which can be used to run external programs. Entries are separated by
|
||||||
|
blank lines.
|
||||||
|
|
||||||
|
Actions are very powerful, anything that you can map to a key combination in
|
||||||
|
`kitty.conf` can be used as an action. You can specify more than one action per
|
||||||
|
entry if you like, for example:
|
||||||
|
|
||||||
|
|
||||||
|
.. code:: conf
|
||||||
|
|
||||||
|
# Tail a log file (*.log) in a new OS Window and reduce its font size
|
||||||
|
protocol file
|
||||||
|
ext log
|
||||||
|
action launch --title ${FILE} --type=os-window tail -f ${FILE_PATH}
|
||||||
|
action change_font_size current -2
|
||||||
|
|
||||||
|
|
||||||
|
In the action specification you can expand environment variables, as shown in
|
||||||
|
the examples above. In addition to regular environment variables, there are
|
||||||
|
some special variables, documented below:
|
||||||
|
|
||||||
|
``URL``
|
||||||
|
The full URL being opened
|
||||||
|
|
||||||
|
``FILE_PATH``
|
||||||
|
The path portion of the URL (unquoted)
|
||||||
|
|
||||||
|
``FILE``
|
||||||
|
The file portion of the path of the URL (unquoted)
|
||||||
|
|
||||||
|
``FRAGMENT``
|
||||||
|
The fragment (unquoted), if any of the URL or the empty string.
|
||||||
|
|
||||||
|
.. _matching_criteria:
|
||||||
|
|
||||||
|
Matching criteria
|
||||||
|
------------------
|
||||||
|
|
||||||
|
An entry in :file:`open-actions.conf` must have one or more matching criteria.
|
||||||
|
URLs that match all criteria for an entry will trigger that entries' actions.
|
||||||
|
Processing stops at the first matching entry, so put more specific matching
|
||||||
|
criteria at the start of the list. Entries in the file are separated by blank
|
||||||
|
lines. The various available criteria are:
|
||||||
|
|
||||||
|
``protocol``
|
||||||
|
A comma separated list of protocols, for example: ``http, https``. If
|
||||||
|
absent, there is no constraint on protocol
|
||||||
|
|
||||||
|
``url``
|
||||||
|
A regular expression that must match against the entire (unquoted) URL
|
||||||
|
|
||||||
|
``fragment_matches``
|
||||||
|
A regular expression that must match against the fragment (part after #) in
|
||||||
|
the URL
|
||||||
|
|
||||||
|
``mime``
|
||||||
|
A comma separated list of MIME types, for example: ``text/*, image/*,
|
||||||
|
application/pdf``
|
||||||
|
|
||||||
|
``ext``
|
||||||
|
A comma separated list of file extensions, for example: ``jpeg, tar.gz``
|
||||||
|
|
||||||
|
``file``
|
||||||
|
A shell glob pattern that must match the filename, for example:
|
||||||
|
``image-??.png``
|
||||||
@@ -222,9 +222,132 @@ Here ``!`` is not valid base64 encoded text, so it clears the clipboard.
|
|||||||
Further, since it is invalid, it should be ignored by terminal emulators
|
Further, since it is invalid, it should be ignored by terminal emulators
|
||||||
that do not support this extension, thereby making it safe to use, simply
|
that do not support this extension, thereby making it safe to use, simply
|
||||||
always send it before starting a new OSC 52 paste, even if you aren't chunking
|
always send it before starting a new OSC 52 paste, even if you aren't chunking
|
||||||
up large pastes, that way kitty wont concatenate your paste, and it will have
|
up large pastes, that way kitty won't concatenate your paste, and it will have
|
||||||
no ill-effects in other terminal emulators.
|
no ill-effects in other terminal emulators.
|
||||||
|
|
||||||
In case you're using software that can't be easily adapted to this
|
In case you're using software that can't be easily adapted to this
|
||||||
protocol extension, it can be disabled by specifying ``no-append`` to the
|
protocol extension, it can be disabled by specifying ``no-append`` to the
|
||||||
:opt:`clipboard_control` setting.
|
:opt:`clipboard_control` setting.
|
||||||
|
|
||||||
|
|
||||||
|
.. _desktop_notifications:
|
||||||
|
|
||||||
|
|
||||||
|
Desktop notifications
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
|kitty| implements an extensible escape code (OSC 99) to show desktop
|
||||||
|
notifications. It is easy to use from shell scripts and fully extensible to
|
||||||
|
show title and body. Clicking on the notification can optionally focus the
|
||||||
|
window it came from, and/or send an escape code back to the application running
|
||||||
|
in that window.
|
||||||
|
|
||||||
|
The design of the escape code is partially based on the discussion in
|
||||||
|
the defunct
|
||||||
|
`terminal-wg <https://gitlab.freedesktop.org/terminal-wg/specifications/-/issues/13>`_
|
||||||
|
|
||||||
|
The escape code has the form::
|
||||||
|
|
||||||
|
<OSC> 99 ; metadata ; payload <terminator>
|
||||||
|
|
||||||
|
Here ``<OSC>`` is :code:`<ESC>]` and ``<terminator>`` is :code:`<ESC><backslash>`. The
|
||||||
|
metadata is a section of colon separated :code:`key=value` pairs. Every key
|
||||||
|
must be a single character from the set :code:`[a-zA-Z]` and every value must
|
||||||
|
be a character from the set :code:`a-zA-Z0-9-_/\+.,(){}[]*&^%$#@!`~`. The
|
||||||
|
payload must be interpreted based on the metadata section. The two semi-colons
|
||||||
|
*must* always be present even when no metadata is present.
|
||||||
|
|
||||||
|
Before going into details, lets see how one can display a simple, single line
|
||||||
|
notification from a shell script::
|
||||||
|
|
||||||
|
printf '\x1b]99;;Hello world\x1b\\'
|
||||||
|
|
||||||
|
To show a message with a title and a body::
|
||||||
|
|
||||||
|
printf '\x1b]99;i=1:d=0;Hello world\x1b\\'
|
||||||
|
printf '\x1b]99;i=1:d=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
|
||||||
|
title for the notification. A value of ``body`` means it is setting the body,
|
||||||
|
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 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 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).
|
||||||
|
|
||||||
|
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
|
||||||
|
it can send back an escape code to the application indicating the notification
|
||||||
|
was activated. This is controlled by the ``a`` key which takes a comma
|
||||||
|
separated set of values, ``report`` and ``focus``. The value ``focus`` means
|
||||||
|
focus the window from which the notification was issued and is the default.
|
||||||
|
``report`` means send an escape code back to the application. The format of the
|
||||||
|
escape code is::
|
||||||
|
|
||||||
|
<OSC> 99 ; i=identifier ; <terminator>
|
||||||
|
|
||||||
|
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``. 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 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.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
It is possible to extend this escape code to allow specifying an icon for
|
||||||
|
the notification, however, given that some platforms, such as macOS, dont
|
||||||
|
allow displaying custom icons on a notification, at all, it was decided to
|
||||||
|
leave it out of the spec for the time being.
|
||||||
|
|
||||||
|
Similarly, features such a scheduled notifications could be added in future
|
||||||
|
revisions.
|
||||||
|
|
||||||
|
|
||||||
|
======= ==================== ========= =================
|
||||||
|
Key Value Default Description
|
||||||
|
======= ==================== ========= =================
|
||||||
|
``a`` Comma separated list ``focus`` What action to perform when the
|
||||||
|
of ``report``, notification is clicked
|
||||||
|
``focus``, with
|
||||||
|
optional leading
|
||||||
|
``-``
|
||||||
|
|
||||||
|
``d`` ``0`` or ``1`` ``1`` Indicates if the notification is
|
||||||
|
complete or not.
|
||||||
|
|
||||||
|
``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
|
||||||
|
|
||||||
|
``i`` ``[a-zA-Z0-9-_+.]`` ``0`` Identifier for the notification
|
||||||
|
|
||||||
|
``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.
|
||||||
|
======= ==================== ========= =================
|
||||||
|
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|kitty| also supports the legacy OSC 9 protocol developed by iTerm2 for
|
||||||
|
desktop notifications.
|
||||||
|
|||||||
@@ -10,18 +10,18 @@ form::
|
|||||||
Where ``<ESC>`` is the byte ``0x1b``. The JSON object has the form::
|
Where ``<ESC>`` is the byte ``0x1b``. The JSON object has the form::
|
||||||
|
|
||||||
{
|
{
|
||||||
'cmd': "command name",
|
"cmd": "command name",
|
||||||
'version': "kitty version",
|
"version": <kitty version>,
|
||||||
'no_response': Optional Boolean,
|
"no_response": Optional Boolean,
|
||||||
'payload': <Optional JSON object>,
|
"payload": <Optional JSON object>,
|
||||||
}
|
}
|
||||||
|
|
||||||
The ``version`` above is a string of the form :code:`0.14.2`. If you are developing a
|
The ``version`` above is an array of the form :code:`[0, 14, 2]`. If you are developing a
|
||||||
standalone client, use the kitty version that you are developing against. Using
|
standalone client, use the kitty version that you are developing against. Using
|
||||||
a version greater than the version of the kitty instance you are talking to,
|
a version greater than the version of the kitty instance you are talking to,
|
||||||
will cause a failure.
|
will cause a failure.
|
||||||
|
|
||||||
Set ``no_response`` to True if you don't want a response from kitty.
|
Set ``no_response`` to ``true`` if you don't want a response from kitty.
|
||||||
|
|
||||||
The optional payload is a JSON object that is specific to the actual command being sent.
|
The optional payload is a JSON object that is specific to the actual command being sent.
|
||||||
The fields in the object for every command are documented below.
|
The fields in the object for every command are documented below.
|
||||||
|
|||||||
BIN
docs/screenshots/remote_file.png
Normal file
BIN
docs/screenshots/remote_file.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
@@ -98,6 +98,18 @@ def parse_ucd() -> None:
|
|||||||
elif category.startswith('S'):
|
elif category.startswith('S'):
|
||||||
all_symbols.add(codepoint)
|
all_symbols.add(codepoint)
|
||||||
|
|
||||||
|
with open('nerd-fonts-glyphs.txt') as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith('#'):
|
||||||
|
continue
|
||||||
|
code, category, name = line.split(' ', 2)
|
||||||
|
codepoint = int(code, 16)
|
||||||
|
if name and codepoint not in name_map:
|
||||||
|
name_map[codepoint] = name.upper()
|
||||||
|
for word in name.lower().split():
|
||||||
|
add_word(word, codepoint)
|
||||||
|
|
||||||
# Some common synonyms
|
# Some common synonyms
|
||||||
word_search_map['bee'] |= word_search_map['honeybee']
|
word_search_map['bee'] |= word_search_map['honeybee']
|
||||||
word_search_map['lambda'] |= word_search_map['lamda']
|
word_search_map['lambda'] |= word_search_map['lamda']
|
||||||
@@ -274,7 +286,9 @@ def category_test(
|
|||||||
comment: str,
|
comment: str,
|
||||||
use_static: bool = False,
|
use_static: bool = False,
|
||||||
extra_chars: Union[FrozenSet[int], Set[int]] = frozenset(),
|
extra_chars: Union[FrozenSet[int], Set[int]] = frozenset(),
|
||||||
exclude: Union[Set[int], FrozenSet[int]] = frozenset()
|
exclude: Union[Set[int], FrozenSet[int]] = frozenset(),
|
||||||
|
least_check_return: Optional[str] = None,
|
||||||
|
ascii_range: Optional[str] = None
|
||||||
) -> None:
|
) -> None:
|
||||||
static = 'static inline ' if use_static else ''
|
static = 'static inline ' if use_static else ''
|
||||||
chars: Set[int] = set()
|
chars: Set[int] = set()
|
||||||
@@ -284,6 +298,11 @@ def category_test(
|
|||||||
chars -= exclude
|
chars -= exclude
|
||||||
p(f'{static}bool\n{name}(char_type code) {{')
|
p(f'{static}bool\n{name}(char_type code) {{')
|
||||||
p(f'\t// {comment} ({len(chars)} codepoints)' + ' {{' '{')
|
p(f'\t// {comment} ({len(chars)} codepoints)' + ' {{' '{')
|
||||||
|
if least_check_return is not None:
|
||||||
|
least = min(chars)
|
||||||
|
p(f'\tif (LIKELY(code < {least})) return {least_check_return};')
|
||||||
|
if ascii_range is not None:
|
||||||
|
p(f'\tif (LIKELY(0x20 <= code && code <= 0x7e)) return {ascii_range};')
|
||||||
p('\tswitch(code) {')
|
p('\tswitch(code) {')
|
||||||
for spec in get_ranges(list(chars)):
|
for spec in get_ranges(list(chars)):
|
||||||
write_case(spec, p)
|
write_case(spec, p)
|
||||||
@@ -337,11 +356,15 @@ def gen_ucd() -> None:
|
|||||||
{c for c in class_maps if c.startswith('M')},
|
{c for c in class_maps if c.startswith('M')},
|
||||||
'M category (marks)',
|
'M category (marks)',
|
||||||
# See https://github.com/harfbuzz/harfbuzz/issues/169
|
# See https://github.com/harfbuzz/harfbuzz/issues/169
|
||||||
extra_chars=emoji_skin_tone_modifiers | {zwj}
|
extra_chars=emoji_skin_tone_modifiers | {zwj},
|
||||||
|
least_check_return='false'
|
||||||
)
|
)
|
||||||
category_test(
|
category_test(
|
||||||
'is_ignored_char', p, 'Cc Cf Cs'.split(),
|
'is_ignored_char', p, 'Cc Cf Cs'.split(),
|
||||||
'Control characters and non-characters', extra_chars=non_characters, exclude={zwj})
|
'Control characters and non-characters',
|
||||||
|
extra_chars=non_characters, exclude={zwj},
|
||||||
|
ascii_range='false'
|
||||||
|
)
|
||||||
category_test('is_word_char', p, {c for c in class_maps if c[0] in 'LN'}, 'L and N categories')
|
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_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)')
|
category_test('is_P_category', p, {c for c in class_maps if c[0] == 'P'}, 'P category (punctuation)')
|
||||||
@@ -421,7 +444,7 @@ def gen_names() -> None:
|
|||||||
p('}; // }}}\n')
|
p('}; // }}}\n')
|
||||||
|
|
||||||
# The trie
|
# The trie
|
||||||
p('typedef struct {{ uint32_t children_offset; uint32_t match_offset; }} word_trie;\n')
|
p('typedef struct { uint32_t children_offset; uint32_t match_offset; } word_trie;\n')
|
||||||
all_trie_nodes: List['TrieNode'] = [] # noqa
|
all_trie_nodes: List['TrieNode'] = [] # noqa
|
||||||
|
|
||||||
class TrieNode:
|
class TrieNode:
|
||||||
@@ -482,6 +505,7 @@ def gen_wcwidth() -> None:
|
|||||||
|
|
||||||
with create_header('kitty/wcwidth-std.h') as p:
|
with create_header('kitty/wcwidth-std.h') as p:
|
||||||
p('static int\nwcwidth_std(int32_t code) {')
|
p('static int\nwcwidth_std(int32_t code) {')
|
||||||
|
p('\tif (LIKELY(0x20 <= code && code <= 0x7e)) return 1;')
|
||||||
p('\tswitch(code) {')
|
p('\tswitch(code) {')
|
||||||
|
|
||||||
non_printing = class_maps['Cc'] | class_maps['Cf'] | class_maps['Cs']
|
non_printing = class_maps['Cc'] | class_maps['Cf'] | class_maps['Cs']
|
||||||
@@ -500,7 +524,7 @@ def gen_wcwidth() -> None:
|
|||||||
p('\t}')
|
p('\t}')
|
||||||
p('\treturn 1;\n}')
|
p('\treturn 1;\n}')
|
||||||
|
|
||||||
p('static bool\nis_emoji_presentation_base(uint32_t code) {')
|
p('static inline bool\nis_emoji_presentation_base(uint32_t code) {')
|
||||||
p('\tswitch(code) {')
|
p('\tswitch(code) {')
|
||||||
for spec in get_ranges(list(emoji_presentation_bases)):
|
for spec in get_ranges(list(emoji_presentation_bases)):
|
||||||
write_case(spec, p)
|
write_case(spec, p)
|
||||||
|
|||||||
@@ -669,8 +669,6 @@ int _glfwPlatformInit(void)
|
|||||||
if (!initializeTIS())
|
if (!initializeTIS())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
_glfwInitJoysticksNS();
|
|
||||||
|
|
||||||
_glfwPollMonitorsNS();
|
_glfwPollMonitorsNS();
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@@ -723,7 +721,6 @@ void _glfwPlatformTerminate(void)
|
|||||||
free(_glfw.ns.clipboardString);
|
free(_glfw.ns.clipboardString);
|
||||||
|
|
||||||
_glfwTerminateNSGL();
|
_glfwTerminateNSGL();
|
||||||
_glfwTerminateJoysticksNS();
|
|
||||||
|
|
||||||
} // autoreleasepool
|
} // autoreleasepool
|
||||||
}
|
}
|
||||||
|
|||||||
4
glfw/cocoa_joystick.h
vendored
4
glfw/cocoa_joystick.h
vendored
@@ -44,7 +44,3 @@ typedef struct _GLFWjoystickNS
|
|||||||
CFMutableArrayRef hats;
|
CFMutableArrayRef hats;
|
||||||
} _GLFWjoystickNS;
|
} _GLFWjoystickNS;
|
||||||
|
|
||||||
|
|
||||||
void _glfwInitJoysticksNS(void);
|
|
||||||
void _glfwTerminateJoysticksNS(void);
|
|
||||||
|
|
||||||
|
|||||||
@@ -233,6 +233,19 @@ static void matchCallback(void* context UNUSED,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (page == kHIDPage_Simulation)
|
||||||
|
{
|
||||||
|
switch (usage)
|
||||||
|
{
|
||||||
|
case kHIDUsage_Sim_Accelerator:
|
||||||
|
case kHIDUsage_Sim_Brake:
|
||||||
|
case kHIDUsage_Sim_Throttle:
|
||||||
|
case kHIDUsage_Sim_Rudder:
|
||||||
|
case kHIDUsage_Sim_Steering:
|
||||||
|
target = axes;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
else if (page == kHIDPage_Button || page == kHIDPage_Consumer)
|
else if (page == kHIDPage_Button || page == kHIDPage_Consumer)
|
||||||
target = buttons;
|
target = buttons;
|
||||||
|
|
||||||
@@ -291,12 +304,10 @@ static void removeCallback(void* context UNUSED,
|
|||||||
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
////// GLFW internal API //////
|
////// GLFW platform API //////
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// Initialize joystick interface
|
bool _glfwPlatformInitJoysticks(void)
|
||||||
//
|
|
||||||
void _glfwInitJoysticksNS(void)
|
|
||||||
{
|
{
|
||||||
CFMutableArrayRef matching;
|
CFMutableArrayRef matching;
|
||||||
const long usages[] =
|
const long usages[] =
|
||||||
@@ -315,7 +326,7 @@ void _glfwInitJoysticksNS(void)
|
|||||||
if (!matching)
|
if (!matching)
|
||||||
{
|
{
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create array");
|
_glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create array");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < sizeof(usages) / sizeof(long); i++)
|
for (size_t i = 0; i < sizeof(usages) / sizeof(long); i++)
|
||||||
@@ -370,26 +381,24 @@ void _glfwInitJoysticksNS(void)
|
|||||||
// Execute the run loop once in order to register any initially-attached
|
// Execute the run loop once in order to register any initially-attached
|
||||||
// joysticks
|
// joysticks
|
||||||
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
|
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close all opened joystick handles
|
void _glfwPlatformTerminateJoysticks(void)
|
||||||
//
|
|
||||||
void _glfwTerminateJoysticksNS(void)
|
|
||||||
{
|
{
|
||||||
int jid;
|
int jid;
|
||||||
|
|
||||||
for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++)
|
for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++)
|
||||||
closeJoystick(_glfw.joysticks + jid);
|
closeJoystick(_glfw.joysticks + jid);
|
||||||
|
|
||||||
CFRelease(_glfw.ns.hidManager);
|
if (_glfw.ns.hidManager)
|
||||||
_glfw.ns.hidManager = NULL;
|
{
|
||||||
|
CFRelease(_glfw.ns.hidManager);
|
||||||
|
_glfw.ns.hidManager = NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
|
||||||
////// GLFW platform API //////
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode)
|
int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode)
|
||||||
{
|
{
|
||||||
if (mode & _GLFW_POLL_AXES)
|
if (mode & _GLFW_POLL_AXES)
|
||||||
|
|||||||
@@ -279,14 +279,20 @@ static double getFallbackRefreshRate(CGDirectDisplayID displayID)
|
|||||||
CFSTR("IOFBCurrentPixelCount"),
|
CFSTR("IOFBCurrentPixelCount"),
|
||||||
kCFAllocatorDefault,
|
kCFAllocatorDefault,
|
||||||
kNilOptions);
|
kNilOptions);
|
||||||
if (!clockRef || !countRef)
|
|
||||||
break;
|
|
||||||
|
|
||||||
uint32_t clock = 0, count = 0;
|
uint32_t clock = 0, count = 0;
|
||||||
CFNumberGetValue(clockRef, kCFNumberIntType, &clock);
|
|
||||||
CFNumberGetValue(countRef, kCFNumberIntType, &count);
|
if (clockRef)
|
||||||
CFRelease(clockRef);
|
{
|
||||||
CFRelease(countRef);
|
CFNumberGetValue(clockRef, kCFNumberIntType, &clock);
|
||||||
|
CFRelease(clockRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (countRef)
|
||||||
|
{
|
||||||
|
CFNumberGetValue(countRef, kCFNumberIntType, &count);
|
||||||
|
CFRelease(countRef);
|
||||||
|
}
|
||||||
|
|
||||||
if (clock > 0 && count > 0)
|
if (clock > 0 && count > 0)
|
||||||
refreshRate = clock / (double) count;
|
refreshRate = clock / (double) count;
|
||||||
|
|||||||
5
glfw/cocoa_platform.h
vendored
5
glfw/cocoa_platform.h
vendored
@@ -96,16 +96,11 @@ typedef VkResult (APIENTRY *PFN_vkCreateMetalSurfaceEXT)(VkInstance,const VkMeta
|
|||||||
#include "posix_thread.h"
|
#include "posix_thread.h"
|
||||||
#include "cocoa_joystick.h"
|
#include "cocoa_joystick.h"
|
||||||
#include "nsgl_context.h"
|
#include "nsgl_context.h"
|
||||||
#include "egl_context.h"
|
|
||||||
#include "osmesa_context.h"
|
|
||||||
|
|
||||||
#define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL)
|
#define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL)
|
||||||
#define _glfw_dlclose(handle) dlclose(handle)
|
#define _glfw_dlclose(handle) dlclose(handle)
|
||||||
#define _glfw_dlsym(handle, name) dlsym(handle, name)
|
#define _glfw_dlsym(handle, name) dlsym(handle, name)
|
||||||
|
|
||||||
#define _GLFW_EGL_NATIVE_WINDOW ((EGLNativeWindowType) window->ns.layer)
|
|
||||||
#define _GLFW_EGL_NATIVE_DISPLAY EGL_DEFAULT_DISPLAY
|
|
||||||
|
|
||||||
#define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowNS ns
|
#define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowNS ns
|
||||||
#define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryNS ns
|
#define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryNS ns
|
||||||
#define _GLFW_PLATFORM_LIBRARY_TIMER_STATE _GLFWtimerNS ns
|
#define _GLFW_PLATFORM_LIBRARY_TIMER_STATE _GLFWtimerNS ns
|
||||||
|
|||||||
@@ -1854,6 +1854,11 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled)
|
|||||||
[window->ns.object setLevel:NSNormalWindowLevel];
|
[window->ns.object setLevel:NSNormalWindowLevel];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled)
|
||||||
|
{
|
||||||
|
[window->ns.object setIgnoresMouseEvents:enabled];
|
||||||
|
}
|
||||||
|
|
||||||
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window)
|
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window)
|
||||||
{
|
{
|
||||||
return (float) [window->ns.object alphaValue];
|
return (float) [window->ns.object alphaValue];
|
||||||
@@ -2105,6 +2110,47 @@ const char* _glfwPlatformGetClipboardString(void)
|
|||||||
return _glfw.ns.clipboardString;
|
return _glfw.ns.clipboardString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs)
|
||||||
|
{
|
||||||
|
if (_glfw.egl.ANGLE_platform_angle)
|
||||||
|
{
|
||||||
|
int type = 0;
|
||||||
|
|
||||||
|
if (_glfw.egl.ANGLE_platform_angle_opengl)
|
||||||
|
{
|
||||||
|
if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_OPENGL)
|
||||||
|
type = EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_glfw.egl.ANGLE_platform_angle_metal)
|
||||||
|
{
|
||||||
|
if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_METAL)
|
||||||
|
type = EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type)
|
||||||
|
{
|
||||||
|
*attribs = calloc(3, sizeof(EGLint));
|
||||||
|
(*attribs)[0] = EGL_PLATFORM_ANGLE_TYPE_ANGLE;
|
||||||
|
(*attribs)[1] = type;
|
||||||
|
(*attribs)[2] = EGL_NONE;
|
||||||
|
return EGL_PLATFORM_ANGLE_ANGLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void)
|
||||||
|
{
|
||||||
|
return EGL_DEFAULT_DISPLAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window)
|
||||||
|
{
|
||||||
|
return window->ns.layer;
|
||||||
|
}
|
||||||
|
|
||||||
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
|
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
|
||||||
{
|
{
|
||||||
if (_glfw.vk.KHR_surface && _glfw.vk.EXT_metal_surface)
|
if (_glfw.vk.KHR_surface && _glfw.vk.EXT_metal_surface)
|
||||||
|
|||||||
68
glfw/egl_context.c
vendored
68
glfw/egl_context.c
vendored
@@ -273,6 +273,8 @@ static void destroyContextEGL(_GLFWwindow* window)
|
|||||||
bool _glfwInitEGL(void)
|
bool _glfwInitEGL(void)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
EGLint* attribs = NULL;
|
||||||
|
const char* extensions;
|
||||||
const char* sonames[] =
|
const char* sonames[] =
|
||||||
{
|
{
|
||||||
#if defined(_GLFW_EGL_LIBRARY)
|
#if defined(_GLFW_EGL_LIBRARY)
|
||||||
@@ -351,7 +353,51 @@ bool _glfwInitEGL(void)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_glfw.egl.display = eglGetDisplay(_GLFW_EGL_NATIVE_DISPLAY);
|
extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
|
||||||
|
if (extensions && eglGetError() == EGL_SUCCESS)
|
||||||
|
_glfw.egl.EXT_client_extensions = true;
|
||||||
|
|
||||||
|
if (_glfw.egl.EXT_client_extensions)
|
||||||
|
{
|
||||||
|
_glfw.egl.EXT_platform_base =
|
||||||
|
_glfwStringInExtensionString("EGL_EXT_platform_base", extensions);
|
||||||
|
_glfw.egl.EXT_platform_x11 =
|
||||||
|
_glfwStringInExtensionString("EGL_EXT_platform_x11", extensions);
|
||||||
|
_glfw.egl.EXT_platform_wayland =
|
||||||
|
_glfwStringInExtensionString("EGL_EXT_platform_wayland", extensions);
|
||||||
|
_glfw.egl.ANGLE_platform_angle =
|
||||||
|
_glfwStringInExtensionString("EGL_ANGLE_platform_angle", extensions);
|
||||||
|
_glfw.egl.ANGLE_platform_angle_opengl =
|
||||||
|
_glfwStringInExtensionString("EGL_ANGLE_platform_angle_opengl", extensions);
|
||||||
|
_glfw.egl.ANGLE_platform_angle_d3d =
|
||||||
|
_glfwStringInExtensionString("EGL_ANGLE_platform_angle_d3d", extensions);
|
||||||
|
_glfw.egl.ANGLE_platform_angle_vulkan =
|
||||||
|
_glfwStringInExtensionString("EGL_ANGLE_platform_angle_vulkan", extensions);
|
||||||
|
_glfw.egl.ANGLE_platform_angle_metal =
|
||||||
|
_glfwStringInExtensionString("EGL_ANGLE_platform_angle_metal", extensions);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_glfw.egl.EXT_platform_base)
|
||||||
|
{
|
||||||
|
_glfw.egl.GetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)
|
||||||
|
eglGetProcAddress("eglGetPlatformDisplayEXT");
|
||||||
|
_glfw.egl.CreatePlatformWindowSurfaceEXT = (PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)
|
||||||
|
eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT");
|
||||||
|
}
|
||||||
|
|
||||||
|
_glfw.egl.platform = _glfwPlatformGetEGLPlatform(&attribs);
|
||||||
|
if (_glfw.egl.platform)
|
||||||
|
{
|
||||||
|
_glfw.egl.display =
|
||||||
|
eglGetPlatformDisplayEXT(_glfw.egl.platform,
|
||||||
|
_glfwPlatformGetEGLNativeDisplay(),
|
||||||
|
attribs);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_glfw.egl.display = eglGetDisplay(_glfwPlatformGetEGLNativeDisplay());
|
||||||
|
|
||||||
|
free(attribs);
|
||||||
|
|
||||||
if (_glfw.egl.display == EGL_NO_DISPLAY)
|
if (_glfw.egl.display == EGL_NO_DISPLAY)
|
||||||
{
|
{
|
||||||
_glfwInputError(GLFW_API_UNAVAILABLE,
|
_glfwInputError(GLFW_API_UNAVAILABLE,
|
||||||
@@ -419,6 +465,7 @@ bool _glfwCreateContextEGL(_GLFWwindow* window,
|
|||||||
EGLint attribs[40];
|
EGLint attribs[40];
|
||||||
EGLConfig config;
|
EGLConfig config;
|
||||||
EGLContext share = NULL;
|
EGLContext share = NULL;
|
||||||
|
EGLNativeWindowType native;
|
||||||
int index = 0;
|
int index = 0;
|
||||||
|
|
||||||
if (!_glfw.egl.display)
|
if (!_glfw.egl.display)
|
||||||
@@ -554,11 +601,20 @@ bool _glfwCreateContextEGL(_GLFWwindow* window,
|
|||||||
|
|
||||||
setAttrib(EGL_NONE, EGL_NONE);
|
setAttrib(EGL_NONE, EGL_NONE);
|
||||||
|
|
||||||
window->context.egl.surface =
|
native = _glfwPlatformGetEGLNativeWindow(window);
|
||||||
eglCreateWindowSurface(_glfw.egl.display,
|
// HACK: ANGLE does not implement eglCreatePlatformWindowSurfaceEXT
|
||||||
config,
|
// despite reporting EGL_EXT_platform_base
|
||||||
_GLFW_EGL_NATIVE_WINDOW,
|
if (_glfw.egl.platform && _glfw.egl.platform != EGL_PLATFORM_ANGLE_ANGLE)
|
||||||
attribs);
|
{
|
||||||
|
window->context.egl.surface =
|
||||||
|
eglCreatePlatformWindowSurfaceEXT(_glfw.egl.display, config, native, attribs);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
window->context.egl.surface =
|
||||||
|
eglCreateWindowSurface(_glfw.egl.display, config, native, attribs);
|
||||||
|
}
|
||||||
|
|
||||||
if (window->context.egl.surface == EGL_NO_SURFACE)
|
if (window->context.egl.surface == EGL_NO_SURFACE)
|
||||||
{
|
{
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||||
|
|||||||
54
glfw/egl_context.h
vendored
54
glfw/egl_context.h
vendored
@@ -25,26 +25,10 @@
|
|||||||
//
|
//
|
||||||
//========================================================================
|
//========================================================================
|
||||||
|
|
||||||
#if defined(_GLFW_USE_EGLPLATFORM_H)
|
#if defined(_GLFW_WIN32)
|
||||||
#include <EGL/eglplatform.h>
|
|
||||||
#elif defined(_GLFW_WIN32)
|
|
||||||
#define EGLAPIENTRY __stdcall
|
#define EGLAPIENTRY __stdcall
|
||||||
typedef HDC EGLNativeDisplayType;
|
|
||||||
typedef HWND EGLNativeWindowType;
|
|
||||||
#elif defined(_GLFW_COCOA)
|
|
||||||
#define EGLAPIENTRY
|
|
||||||
typedef void* EGLNativeDisplayType;
|
|
||||||
typedef id EGLNativeWindowType;
|
|
||||||
#elif defined(_GLFW_X11)
|
|
||||||
#define EGLAPIENTRY
|
|
||||||
typedef Display* EGLNativeDisplayType;
|
|
||||||
typedef Window EGLNativeWindowType;
|
|
||||||
#elif defined(_GLFW_WAYLAND)
|
|
||||||
#define EGLAPIENTRY
|
|
||||||
typedef struct wl_display* EGLNativeDisplayType;
|
|
||||||
typedef struct wl_egl_window* EGLNativeWindowType;
|
|
||||||
#else
|
#else
|
||||||
#error "No supported EGL platform selected"
|
#define EGLAPIENTRY
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define EGL_SUCCESS 0x3000
|
#define EGL_SUCCESS 0x3000
|
||||||
@@ -108,6 +92,17 @@ typedef struct wl_egl_window* EGLNativeWindowType;
|
|||||||
#define EGL_CONTEXT_RELEASE_BEHAVIOR_KHR 0x2097
|
#define EGL_CONTEXT_RELEASE_BEHAVIOR_KHR 0x2097
|
||||||
#define EGL_CONTEXT_RELEASE_BEHAVIOR_NONE_KHR 0
|
#define EGL_CONTEXT_RELEASE_BEHAVIOR_NONE_KHR 0
|
||||||
#define EGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR 0x2098
|
#define EGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR 0x2098
|
||||||
|
#define EGL_PLATFORM_X11_EXT 0x31d5
|
||||||
|
#define EGL_PLATFORM_WAYLAND_EXT 0x31d8
|
||||||
|
#define EGL_PLATFORM_ANGLE_ANGLE 0x3202
|
||||||
|
#define EGL_PLATFORM_ANGLE_TYPE_ANGLE 0x3203
|
||||||
|
#define EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE 0x320d
|
||||||
|
#define EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE 0x320e
|
||||||
|
#define EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE 0x3207
|
||||||
|
#define EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE 0x3208
|
||||||
|
#define EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE 0x3450
|
||||||
|
#define EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE 0x3489
|
||||||
|
#define EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE 0x348f
|
||||||
|
|
||||||
typedef int EGLint;
|
typedef int EGLint;
|
||||||
typedef unsigned int EGLBoolean;
|
typedef unsigned int EGLBoolean;
|
||||||
@@ -117,6 +112,9 @@ typedef void* EGLContext;
|
|||||||
typedef void* EGLDisplay;
|
typedef void* EGLDisplay;
|
||||||
typedef void* EGLSurface;
|
typedef void* EGLSurface;
|
||||||
|
|
||||||
|
typedef void* EGLNativeDisplayType;
|
||||||
|
typedef void* EGLNativeWindowType;
|
||||||
|
|
||||||
// EGL function pointer typedefs
|
// EGL function pointer typedefs
|
||||||
typedef EGLBoolean (EGLAPIENTRY * PFN_eglGetConfigAttrib)(EGLDisplay,EGLConfig,EGLint,EGLint*);
|
typedef EGLBoolean (EGLAPIENTRY * PFN_eglGetConfigAttrib)(EGLDisplay,EGLConfig,EGLint,EGLint*);
|
||||||
typedef EGLBoolean (EGLAPIENTRY * PFN_eglGetConfigs)(EGLDisplay,EGLConfig*,EGLint,EGLint*);
|
typedef EGLBoolean (EGLAPIENTRY * PFN_eglGetConfigs)(EGLDisplay,EGLConfig*,EGLint,EGLint*);
|
||||||
@@ -153,9 +151,10 @@ typedef GLFWglproc (EGLAPIENTRY * PFN_eglGetProcAddress)(const char*);
|
|||||||
#define eglQueryString _glfw.egl.QueryString
|
#define eglQueryString _glfw.egl.QueryString
|
||||||
#define eglGetProcAddress _glfw.egl.GetProcAddress
|
#define eglGetProcAddress _glfw.egl.GetProcAddress
|
||||||
|
|
||||||
#define _GLFW_EGL_CONTEXT_STATE _GLFWcontextEGL egl
|
typedef EGLDisplay (EGLAPIENTRY * PFNEGLGETPLATFORMDISPLAYEXTPROC)(EGLenum,void*,const EGLint*);
|
||||||
#define _GLFW_EGL_LIBRARY_CONTEXT_STATE _GLFWlibraryEGL egl
|
typedef EGLSurface (EGLAPIENTRY * PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)(EGLDisplay,EGLConfig,void*,const EGLint*);
|
||||||
|
#define eglGetPlatformDisplayEXT _glfw.egl.GetPlatformDisplayEXT
|
||||||
|
#define eglCreatePlatformWindowSurfaceEXT _glfw.egl.CreatePlatformWindowSurfaceEXT
|
||||||
|
|
||||||
// EGL-specific per-context data
|
// EGL-specific per-context data
|
||||||
//
|
//
|
||||||
@@ -173,6 +172,7 @@ typedef struct _GLFWcontextEGL
|
|||||||
//
|
//
|
||||||
typedef struct _GLFWlibraryEGL
|
typedef struct _GLFWlibraryEGL
|
||||||
{
|
{
|
||||||
|
EGLenum platform;
|
||||||
EGLDisplay display;
|
EGLDisplay display;
|
||||||
EGLint major, minor;
|
EGLint major, minor;
|
||||||
bool prefix;
|
bool prefix;
|
||||||
@@ -182,6 +182,15 @@ typedef struct _GLFWlibraryEGL
|
|||||||
bool KHR_gl_colorspace;
|
bool KHR_gl_colorspace;
|
||||||
bool KHR_get_all_proc_addresses;
|
bool KHR_get_all_proc_addresses;
|
||||||
bool KHR_context_flush_control;
|
bool KHR_context_flush_control;
|
||||||
|
bool EXT_client_extensions;
|
||||||
|
bool EXT_platform_base;
|
||||||
|
bool EXT_platform_x11;
|
||||||
|
bool EXT_platform_wayland;
|
||||||
|
bool ANGLE_platform_angle;
|
||||||
|
bool ANGLE_platform_angle_opengl;
|
||||||
|
bool ANGLE_platform_angle_d3d;
|
||||||
|
bool ANGLE_platform_angle_vulkan;
|
||||||
|
bool ANGLE_platform_angle_metal;
|
||||||
|
|
||||||
void* handle;
|
void* handle;
|
||||||
|
|
||||||
@@ -203,6 +212,9 @@ typedef struct _GLFWlibraryEGL
|
|||||||
PFN_eglQueryString QueryString;
|
PFN_eglQueryString QueryString;
|
||||||
PFN_eglGetProcAddress GetProcAddress;
|
PFN_eglGetProcAddress GetProcAddress;
|
||||||
|
|
||||||
|
PFNEGLGETPLATFORMDISPLAYEXTPROC GetPlatformDisplayEXT;
|
||||||
|
PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC CreatePlatformWindowSurfaceEXT;
|
||||||
|
|
||||||
} _GLFWlibraryEGL;
|
} _GLFWlibraryEGL;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from typing import Callable, Dict, List, Optional, Tuple
|
|||||||
|
|
||||||
_plat = sys.platform.lower()
|
_plat = sys.platform.lower()
|
||||||
is_linux = 'linux' in _plat
|
is_linux = 'linux' in _plat
|
||||||
|
is_openbsd = 'openbsd' in _plat
|
||||||
base = os.path.dirname(os.path.abspath(__file__))
|
base = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
|
||||||
@@ -72,8 +73,9 @@ def init_env(env: Env, pkg_config: Callable, at_least_version: Callable, test_co
|
|||||||
|
|
||||||
if module in ('x11', 'wayland'):
|
if module in ('x11', 'wayland'):
|
||||||
ans.cflags.append('-pthread')
|
ans.cflags.append('-pthread')
|
||||||
ans.ldpaths.append('-pthread')
|
ans.ldpaths.extend('-pthread -lm'.split())
|
||||||
ans.ldpaths.extend('-lrt -lm -ldl'.split())
|
if not is_openbsd:
|
||||||
|
ans.ldpaths.extend('-lrt -ldl'.split())
|
||||||
at_least_version('xkbcommon', 0, 5)
|
at_least_version('xkbcommon', 0, 5)
|
||||||
|
|
||||||
if module == 'x11':
|
if module == 'x11':
|
||||||
|
|||||||
44
glfw/glfw3.h
vendored
44
glfw/glfw3.h
vendored
@@ -831,7 +831,7 @@ extern "C" {
|
|||||||
* @analysis Application programmer error. Fix the offending call.
|
* @analysis Application programmer error. Fix the offending call.
|
||||||
*/
|
*/
|
||||||
#define GLFW_NO_WINDOW_CONTEXT 0x0001000A
|
#define GLFW_NO_WINDOW_CONTEXT 0x0001000A
|
||||||
/*! @brief The reuqested feature is not provided by the platform.
|
/*! @brief The requested feature is not provided by the platform.
|
||||||
*
|
*
|
||||||
* The requested feature is not provided by the platform, so GLFW is unable to
|
* The requested feature is not provided by the platform, so GLFW is unable to
|
||||||
* implement it. The documentation for each function notes if it could emit
|
* implement it. The documentation for each function notes if it could emit
|
||||||
@@ -932,11 +932,19 @@ extern "C" {
|
|||||||
* [window attribute](@ref GLFW_FOCUS_ON_SHOW_attrib).
|
* [window attribute](@ref GLFW_FOCUS_ON_SHOW_attrib).
|
||||||
*/
|
*/
|
||||||
#define GLFW_FOCUS_ON_SHOW 0x0002000C
|
#define GLFW_FOCUS_ON_SHOW 0x0002000C
|
||||||
|
|
||||||
|
/*! @brief Mouse input transparency window hint and attribute
|
||||||
|
*
|
||||||
|
* Mouse input transparency [window hint](@ref GLFW_MOUSE_PASSTHROUGH_hint) or
|
||||||
|
* [window attribute](@ref GLFW_MOUSE_PASSTHROUGH_attrib).
|
||||||
|
*/
|
||||||
|
#define GLFW_MOUSE_PASSTHROUGH 0x0002000D
|
||||||
|
|
||||||
/*! @brief Occlusion window attribute
|
/*! @brief Occlusion window attribute
|
||||||
*
|
*
|
||||||
* Occlusion [window attribute](@ref GLFW_OCCLUDED_attrib).
|
* Occlusion [window attribute](@ref GLFW_OCCLUDED_attrib).
|
||||||
*/
|
*/
|
||||||
#define GLFW_OCCLUDED 0x0002000D
|
#define GLFW_OCCLUDED 0x0002000E
|
||||||
/*! @brief Framebuffer bit depth hint.
|
/*! @brief Framebuffer bit depth hint.
|
||||||
*
|
*
|
||||||
* Framebuffer bit depth [hint](@ref GLFW_RED_BITS).
|
* Framebuffer bit depth [hint](@ref GLFW_RED_BITS).
|
||||||
@@ -1054,12 +1062,17 @@ extern "C" {
|
|||||||
* and [attribute](@ref GLFW_OPENGL_FORWARD_COMPAT_attrib).
|
* and [attribute](@ref GLFW_OPENGL_FORWARD_COMPAT_attrib).
|
||||||
*/
|
*/
|
||||||
#define GLFW_OPENGL_FORWARD_COMPAT 0x00022006
|
#define GLFW_OPENGL_FORWARD_COMPAT 0x00022006
|
||||||
/*! @brief OpenGL debug context hint and attribute.
|
/*! @brief Debug mode context hint and attribute.
|
||||||
*
|
*
|
||||||
* OpenGL debug context [hint](@ref GLFW_OPENGL_DEBUG_CONTEXT_hint) and
|
* Debug mode context [hint](@ref GLFW_CONTEXT_DEBUG_hint) and
|
||||||
* [attribute](@ref GLFW_OPENGL_DEBUG_CONTEXT_attrib).
|
* [attribute](@ref GLFW_CONTEXT_DEBUG_attrib).
|
||||||
*/
|
*/
|
||||||
#define GLFW_OPENGL_DEBUG_CONTEXT 0x00022007
|
#define GLFW_CONTEXT_DEBUG 0x00022007
|
||||||
|
/*! @brief Legacy name for compatibility.
|
||||||
|
*
|
||||||
|
* This is an alias for compatibility with earlier versions.
|
||||||
|
*/
|
||||||
|
#define GLFW_OPENGL_DEBUG_CONTEXT GLFW_CONTEXT_DEBUG
|
||||||
/*! @brief OpenGL profile hint and attribute.
|
/*! @brief OpenGL profile hint and attribute.
|
||||||
*
|
*
|
||||||
* OpenGL profile [hint](@ref GLFW_OPENGL_PROFILE_hint) and
|
* OpenGL profile [hint](@ref GLFW_OPENGL_PROFILE_hint) and
|
||||||
@@ -1142,6 +1155,14 @@ extern "C" {
|
|||||||
#define GLFW_EGL_CONTEXT_API 0x00036002
|
#define GLFW_EGL_CONTEXT_API 0x00036002
|
||||||
#define GLFW_OSMESA_CONTEXT_API 0x00036003
|
#define GLFW_OSMESA_CONTEXT_API 0x00036003
|
||||||
|
|
||||||
|
#define GLFW_ANGLE_PLATFORM_TYPE_NONE 0x00037001
|
||||||
|
#define GLFW_ANGLE_PLATFORM_TYPE_OPENGL 0x00037002
|
||||||
|
#define GLFW_ANGLE_PLATFORM_TYPE_OPENGLES 0x00037003
|
||||||
|
#define GLFW_ANGLE_PLATFORM_TYPE_D3D9 0x00037004
|
||||||
|
#define GLFW_ANGLE_PLATFORM_TYPE_D3D11 0x00037005
|
||||||
|
#define GLFW_ANGLE_PLATFORM_TYPE_VULKAN 0x00037007
|
||||||
|
#define GLFW_ANGLE_PLATFORM_TYPE_METAL 0x00037008
|
||||||
|
|
||||||
/*! @defgroup shapes Standard cursor shapes
|
/*! @defgroup shapes Standard cursor shapes
|
||||||
* @brief Standard system cursor shapes.
|
* @brief Standard system cursor shapes.
|
||||||
*
|
*
|
||||||
@@ -1175,8 +1196,12 @@ typedef enum {
|
|||||||
* Joystick hat buttons [init hint](@ref GLFW_JOYSTICK_HAT_BUTTONS).
|
* Joystick hat buttons [init hint](@ref GLFW_JOYSTICK_HAT_BUTTONS).
|
||||||
*/
|
*/
|
||||||
#define GLFW_JOYSTICK_HAT_BUTTONS 0x00050001
|
#define GLFW_JOYSTICK_HAT_BUTTONS 0x00050001
|
||||||
#define GLFW_DEBUG_KEYBOARD 0x00050002
|
/*! @brief ANGLE rendering backend init hint.
|
||||||
#define GLFW_ENABLE_JOYSTICKS 0x00050003
|
*
|
||||||
|
* ANGLE rendering backend [init hint](@ref GLFW_ANGLE_PLATFORM_TYPE_hint).
|
||||||
|
*/
|
||||||
|
#define GLFW_ANGLE_PLATFORM_TYPE 0x00050002
|
||||||
|
#define GLFW_DEBUG_KEYBOARD 0x00050003
|
||||||
/*! @brief macOS specific init hint.
|
/*! @brief macOS specific init hint.
|
||||||
*
|
*
|
||||||
* macOS specific [init hint](@ref GLFW_COCOA_CHDIR_RESOURCES_hint).
|
* macOS specific [init hint](@ref GLFW_COCOA_CHDIR_RESOURCES_hint).
|
||||||
@@ -1477,7 +1502,7 @@ typedef void (* GLFWwindowiconifyfun)(GLFWwindow*,int);
|
|||||||
* @endcode
|
* @endcode
|
||||||
*
|
*
|
||||||
* @param[in] window The window that was maximized or restored.
|
* @param[in] window The window that was maximized or restored.
|
||||||
* @param[in] iconified `true` if the window was maximized, or
|
* @param[in] maximized `true` if the window was maximized, or
|
||||||
* `false` if it was restored.
|
* `false` if it was restored.
|
||||||
*
|
*
|
||||||
* @sa @ref window_maximize
|
* @sa @ref window_maximize
|
||||||
@@ -3723,6 +3748,7 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* window, int attrib);
|
|||||||
* [GLFW_FLOATING](@ref GLFW_FLOATING_attrib),
|
* [GLFW_FLOATING](@ref GLFW_FLOATING_attrib),
|
||||||
* [GLFW_AUTO_ICONIFY](@ref GLFW_AUTO_ICONIFY_attrib) and
|
* [GLFW_AUTO_ICONIFY](@ref GLFW_AUTO_ICONIFY_attrib) and
|
||||||
* [GLFW_FOCUS_ON_SHOW](@ref GLFW_FOCUS_ON_SHOW_attrib).
|
* [GLFW_FOCUS_ON_SHOW](@ref GLFW_FOCUS_ON_SHOW_attrib).
|
||||||
|
* [GLFW_MOUSE_PASSTHROUGH](@ref GLFW_MOUSE_PASSTHROUGH_attrib)
|
||||||
*
|
*
|
||||||
* Some of these attributes are ignored for full screen windows. The new
|
* Some of these attributes are ignored for full screen windows. The new
|
||||||
* value will take effect if the window is later made windowed.
|
* value will take effect if the window is later made windowed.
|
||||||
|
|||||||
2
glfw/ibus_glfw.c
vendored
2
glfw/ibus_glfw.c
vendored
@@ -269,7 +269,7 @@ setup_connection(_GLFWIBUSData *ibus) {
|
|||||||
ibus->conn = NULL;
|
ibus->conn = NULL;
|
||||||
}
|
}
|
||||||
debug("Connecting to IBUS daemon @ %s for IME input management\n", ibus->address);
|
debug("Connecting to IBUS daemon @ %s for IME input management\n", ibus->address);
|
||||||
ibus->conn = glfw_dbus_connect_to(ibus->address, "Failed to connect to the IBUS daemon, with error", "ibus", false);
|
ibus->conn = glfw_dbus_connect_to(ibus->address, "Failed to connect to the IBUS daemon, with error", "ibus", true);
|
||||||
if (!ibus->conn) return false;
|
if (!ibus->conn) return false;
|
||||||
free((void*)ibus->input_ctx_path); ibus->input_ctx_path = NULL;
|
free((void*)ibus->input_ctx_path); ibus->input_ctx_path = NULL;
|
||||||
if (!glfw_dbus_call_method_with_reply(
|
if (!glfw_dbus_call_method_with_reply(
|
||||||
|
|||||||
9
glfw/init.c
vendored
9
glfw/init.c
vendored
@@ -53,8 +53,8 @@ static GLFWerrorfun _glfwErrorCallback;
|
|||||||
static _GLFWinitconfig _glfwInitHints =
|
static _GLFWinitconfig _glfwInitHints =
|
||||||
{
|
{
|
||||||
true, // hat buttons
|
true, // hat buttons
|
||||||
|
GLFW_ANGLE_PLATFORM_TYPE_NONE, // ANGLE backend
|
||||||
false, // debug keyboard
|
false, // debug keyboard
|
||||||
true, // enable joystick
|
|
||||||
{
|
{
|
||||||
true, // macOS menu bar
|
true, // macOS menu bar
|
||||||
true // macOS bundle chdir
|
true // macOS bundle chdir
|
||||||
@@ -92,6 +92,7 @@ static void terminate(void)
|
|||||||
_glfw.mappingCount = 0;
|
_glfw.mappingCount = 0;
|
||||||
|
|
||||||
_glfwTerminateVulkan();
|
_glfwTerminateVulkan();
|
||||||
|
_glfwPlatformTerminateJoysticks();
|
||||||
_glfwPlatformTerminate();
|
_glfwPlatformTerminate();
|
||||||
|
|
||||||
_glfw.initialized = false;
|
_glfw.initialized = false;
|
||||||
@@ -275,12 +276,12 @@ GLFWAPI void glfwInitHint(int hint, int value)
|
|||||||
{
|
{
|
||||||
switch (hint)
|
switch (hint)
|
||||||
{
|
{
|
||||||
case GLFW_ENABLE_JOYSTICKS:
|
|
||||||
_glfwInitHints.enableJoysticks = value;
|
|
||||||
return;
|
|
||||||
case GLFW_JOYSTICK_HAT_BUTTONS:
|
case GLFW_JOYSTICK_HAT_BUTTONS:
|
||||||
_glfwInitHints.hatButtons = value;
|
_glfwInitHints.hatButtons = value;
|
||||||
return;
|
return;
|
||||||
|
case GLFW_ANGLE_PLATFORM_TYPE:
|
||||||
|
_glfwInitHints.angleType = value;
|
||||||
|
return;
|
||||||
case GLFW_DEBUG_KEYBOARD:
|
case GLFW_DEBUG_KEYBOARD:
|
||||||
_glfwInitHints.debugKeyboard = value;
|
_glfwInitHints.debugKeyboard = value;
|
||||||
return;
|
return;
|
||||||
|
|||||||
47
glfw/input.c
vendored
47
glfw/input.c
vendored
@@ -44,6 +44,22 @@
|
|||||||
#define _GLFW_JOYSTICK_BUTTON 2
|
#define _GLFW_JOYSTICK_BUTTON 2
|
||||||
#define _GLFW_JOYSTICK_HATBIT 3
|
#define _GLFW_JOYSTICK_HATBIT 3
|
||||||
|
|
||||||
|
// Initializes the platform joystick API if it has not been already
|
||||||
|
//
|
||||||
|
static bool initJoysticks(void)
|
||||||
|
{
|
||||||
|
if (!_glfw.joysticksInitialized)
|
||||||
|
{
|
||||||
|
if (!_glfwPlatformInitJoysticks())
|
||||||
|
{
|
||||||
|
_glfwPlatformTerminateJoysticks();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _glfw.joysticksInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Finds a mapping based on joystick GUID
|
// Finds a mapping based on joystick GUID
|
||||||
//
|
//
|
||||||
static _GLFWmapping* findMapping(const char* guid)
|
static _GLFWmapping* findMapping(const char* guid)
|
||||||
@@ -1101,6 +1117,9 @@ GLFWAPI int glfwJoystickPresent(int jid)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!initJoysticks())
|
||||||
|
return false;
|
||||||
|
|
||||||
js = _glfw.joysticks + jid;
|
js = _glfw.joysticks + jid;
|
||||||
if (!js->present)
|
if (!js->present)
|
||||||
return false;
|
return false;
|
||||||
@@ -1126,6 +1145,9 @@ GLFWAPI const float* glfwGetJoystickAxes(int jid, int* count)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!initJoysticks())
|
||||||
|
return NULL;
|
||||||
|
|
||||||
js = _glfw.joysticks + jid;
|
js = _glfw.joysticks + jid;
|
||||||
if (!js->present)
|
if (!js->present)
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -1155,6 +1177,9 @@ GLFWAPI const unsigned char* glfwGetJoystickButtons(int jid, int* count)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!initJoysticks())
|
||||||
|
return NULL;
|
||||||
|
|
||||||
js = _glfw.joysticks + jid;
|
js = _glfw.joysticks + jid;
|
||||||
if (!js->present)
|
if (!js->present)
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -1188,6 +1213,9 @@ GLFWAPI const unsigned char* glfwGetJoystickHats(int jid, int* count)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!initJoysticks())
|
||||||
|
return NULL;
|
||||||
|
|
||||||
js = _glfw.joysticks + jid;
|
js = _glfw.joysticks + jid;
|
||||||
if (!js->present)
|
if (!js->present)
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -1214,6 +1242,9 @@ GLFWAPI const char* glfwGetJoystickName(int jid)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!initJoysticks())
|
||||||
|
return NULL;
|
||||||
|
|
||||||
js = _glfw.joysticks + jid;
|
js = _glfw.joysticks + jid;
|
||||||
if (!js->present)
|
if (!js->present)
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -1239,6 +1270,9 @@ GLFWAPI const char* glfwGetJoystickGUID(int jid)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!initJoysticks())
|
||||||
|
return NULL;
|
||||||
|
|
||||||
js = _glfw.joysticks + jid;
|
js = _glfw.joysticks + jid;
|
||||||
if (!js->present)
|
if (!js->present)
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -1284,6 +1318,10 @@ GLFWAPI void* glfwGetJoystickUserPointer(int jid)
|
|||||||
GLFWAPI GLFWjoystickfun glfwSetJoystickCallback(GLFWjoystickfun cbfun)
|
GLFWAPI GLFWjoystickfun glfwSetJoystickCallback(GLFWjoystickfun cbfun)
|
||||||
{
|
{
|
||||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||||
|
|
||||||
|
if (!initJoysticks())
|
||||||
|
return NULL;
|
||||||
|
|
||||||
_GLFW_SWAP_POINTERS(_glfw.callbacks.joystick, cbfun);
|
_GLFW_SWAP_POINTERS(_glfw.callbacks.joystick, cbfun);
|
||||||
return cbfun;
|
return cbfun;
|
||||||
}
|
}
|
||||||
@@ -1363,6 +1401,9 @@ GLFWAPI int glfwJoystickIsGamepad(int jid)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!initJoysticks())
|
||||||
|
return false;
|
||||||
|
|
||||||
js = _glfw.joysticks + jid;
|
js = _glfw.joysticks + jid;
|
||||||
if (!js->present)
|
if (!js->present)
|
||||||
return false;
|
return false;
|
||||||
@@ -1388,6 +1429,9 @@ GLFWAPI const char* glfwGetGamepadName(int jid)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!initJoysticks())
|
||||||
|
return NULL;
|
||||||
|
|
||||||
js = _glfw.joysticks + jid;
|
js = _glfw.joysticks + jid;
|
||||||
if (!js->present)
|
if (!js->present)
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -1420,6 +1464,9 @@ GLFWAPI int glfwGetGamepadState(int jid, GLFWgamepadstate* state)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!initJoysticks())
|
||||||
|
return false;
|
||||||
|
|
||||||
js = _glfw.joysticks + jid;
|
js = _glfw.joysticks + jid;
|
||||||
if (!js->present)
|
if (!js->present)
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
23
glfw/internal.h
vendored
23
glfw/internal.h
vendored
@@ -198,6 +198,9 @@ typedef void (APIENTRY * PFN_vkVoidFunction)(void);
|
|||||||
#error "No supported window creation API selected"
|
#error "No supported window creation API selected"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "egl_context.h"
|
||||||
|
#include "osmesa_context.h"
|
||||||
|
|
||||||
#define remove_i_from_array(array, i, count) { \
|
#define remove_i_from_array(array, i, count) { \
|
||||||
(count)--; \
|
(count)--; \
|
||||||
if ((i) < (count)) { \
|
if ((i) < (count)) { \
|
||||||
@@ -271,8 +274,8 @@ struct _GLFWerror
|
|||||||
struct _GLFWinitconfig
|
struct _GLFWinitconfig
|
||||||
{
|
{
|
||||||
bool hatButtons;
|
bool hatButtons;
|
||||||
|
int angleType;
|
||||||
bool debugKeyboard;
|
bool debugKeyboard;
|
||||||
bool enableJoysticks;
|
|
||||||
struct {
|
struct {
|
||||||
bool menubar;
|
bool menubar;
|
||||||
bool chdir;
|
bool chdir;
|
||||||
@@ -299,6 +302,7 @@ struct _GLFWwndconfig
|
|||||||
bool maximized;
|
bool maximized;
|
||||||
bool centerCursor;
|
bool centerCursor;
|
||||||
bool focusOnShow;
|
bool focusOnShow;
|
||||||
|
bool mousePassthrough;
|
||||||
bool scaleToMonitor;
|
bool scaleToMonitor;
|
||||||
struct {
|
struct {
|
||||||
bool retina;
|
bool retina;
|
||||||
@@ -392,9 +396,9 @@ struct _GLFWcontext
|
|||||||
// This is defined in the context API's context.h
|
// This is defined in the context API's context.h
|
||||||
_GLFW_PLATFORM_CONTEXT_STATE
|
_GLFW_PLATFORM_CONTEXT_STATE
|
||||||
// This is defined in egl_context.h
|
// This is defined in egl_context.h
|
||||||
_GLFW_EGL_CONTEXT_STATE;
|
_GLFWcontextEGL egl;
|
||||||
// This is defined in osmesa_context.h
|
// This is defined in osmesa_context.h
|
||||||
_GLFW_OSMESA_CONTEXT_STATE;
|
_GLFWcontextOSMesa osmesa;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Window and context structure
|
// Window and context structure
|
||||||
@@ -409,6 +413,7 @@ struct _GLFWwindow
|
|||||||
bool autoIconify;
|
bool autoIconify;
|
||||||
bool floating;
|
bool floating;
|
||||||
bool focusOnShow;
|
bool focusOnShow;
|
||||||
|
bool mousePassthrough;
|
||||||
bool shouldClose;
|
bool shouldClose;
|
||||||
void* userPointer;
|
void* userPointer;
|
||||||
GLFWid id;
|
GLFWid id;
|
||||||
@@ -569,6 +574,7 @@ struct _GLFWlibrary
|
|||||||
_GLFWmonitor** monitors;
|
_GLFWmonitor** monitors;
|
||||||
int monitorCount;
|
int monitorCount;
|
||||||
|
|
||||||
|
bool joysticksInitialized;
|
||||||
_GLFWjoystick joysticks[GLFW_JOYSTICK_LAST + 1];
|
_GLFWjoystick joysticks[GLFW_JOYSTICK_LAST + 1];
|
||||||
_GLFWmapping* mappings;
|
_GLFWmapping* mappings;
|
||||||
int mappingCount;
|
int mappingCount;
|
||||||
@@ -613,9 +619,9 @@ struct _GLFWlibrary
|
|||||||
// This is defined in the platform's joystick.h
|
// This is defined in the platform's joystick.h
|
||||||
_GLFW_PLATFORM_LIBRARY_JOYSTICK_STATE
|
_GLFW_PLATFORM_LIBRARY_JOYSTICK_STATE
|
||||||
// This is defined in egl_context.h
|
// This is defined in egl_context.h
|
||||||
_GLFW_EGL_LIBRARY_CONTEXT_STATE;
|
_GLFWlibraryEGL egl;
|
||||||
// This is defined in osmesa_context.h
|
// This is defined in osmesa_context.h
|
||||||
_GLFW_OSMESA_LIBRARY_CONTEXT_STATE;
|
_GLFWlibraryOSMesa osmesa;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Global state shared between compilation units of GLFW
|
// Global state shared between compilation units of GLFW
|
||||||
@@ -662,6 +668,8 @@ void _glfwPlatformSetPrimarySelectionString(const char* string);
|
|||||||
const char* _glfwPlatformGetPrimarySelectionString(void);
|
const char* _glfwPlatformGetPrimarySelectionString(void);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
bool _glfwPlatformInitJoysticks(void);
|
||||||
|
void _glfwPlatformTerminateJoysticks(void);
|
||||||
int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode);
|
int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode);
|
||||||
void _glfwPlatformUpdateGamepadGUID(char* guid);
|
void _glfwPlatformUpdateGamepadGUID(char* guid);
|
||||||
|
|
||||||
@@ -713,6 +721,7 @@ float _glfwPlatformGetWindowOpacity(_GLFWwindow* window);
|
|||||||
void _glfwPlatformSetWindowResizable(_GLFWwindow* window, bool enabled);
|
void _glfwPlatformSetWindowResizable(_GLFWwindow* window, bool enabled);
|
||||||
void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, bool enabled);
|
void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, bool enabled);
|
||||||
void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled);
|
void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled);
|
||||||
|
void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled);
|
||||||
void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity);
|
void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity);
|
||||||
void _glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, int d);
|
void _glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, int d);
|
||||||
|
|
||||||
@@ -721,6 +730,10 @@ void _glfwPlatformWaitEvents(void);
|
|||||||
void _glfwPlatformWaitEventsTimeout(monotonic_t timeout);
|
void _glfwPlatformWaitEventsTimeout(monotonic_t timeout);
|
||||||
void _glfwPlatformPostEmptyEvent(void);
|
void _glfwPlatformPostEmptyEvent(void);
|
||||||
|
|
||||||
|
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs);
|
||||||
|
EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void);
|
||||||
|
EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window);
|
||||||
|
|
||||||
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions);
|
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions);
|
||||||
int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance,
|
int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance,
|
||||||
VkPhysicalDevice device,
|
VkPhysicalDevice device,
|
||||||
|
|||||||
98
glfw/linux_joystick.c
vendored
98
glfw/linux_joystick.c
vendored
@@ -264,9 +264,50 @@ static int compareJoysticks(const void* fp, const void* sp)
|
|||||||
////// GLFW internal API //////
|
////// GLFW internal API //////
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// Initialize joystick interface
|
void _glfwDetectJoystickConnectionLinux(void)
|
||||||
//
|
{
|
||||||
bool _glfwInitJoysticksLinux(void)
|
if (_glfw.linjs.inotify <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ssize_t offset = 0;
|
||||||
|
char buffer[16384];
|
||||||
|
const ssize_t size = read(_glfw.linjs.inotify, buffer, sizeof(buffer));
|
||||||
|
|
||||||
|
while (size > offset)
|
||||||
|
{
|
||||||
|
regmatch_t match;
|
||||||
|
const struct inotify_event* e = (struct inotify_event*) (buffer + offset);
|
||||||
|
|
||||||
|
offset += sizeof(struct inotify_event) + e->len;
|
||||||
|
|
||||||
|
if (regexec(&_glfw.linjs.regex, e->name, 1, &match, 0) != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
char path[PATH_MAX];
|
||||||
|
snprintf(path, sizeof(path), "/dev/input/%s", e->name);
|
||||||
|
|
||||||
|
if (e->mask & (IN_CREATE | IN_ATTRIB))
|
||||||
|
openJoystickDevice(path);
|
||||||
|
else if (e->mask & IN_DELETE)
|
||||||
|
{
|
||||||
|
for (int jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++)
|
||||||
|
{
|
||||||
|
if (strcmp(_glfw.joysticks[jid].linjs.path, path) == 0)
|
||||||
|
{
|
||||||
|
closeJoystick(_glfw.joysticks + jid);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
////// GLFW platform API //////
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
bool _glfwPlatformInitJoysticks(void)
|
||||||
{
|
{
|
||||||
const char* dirname = "/dev/input";
|
const char* dirname = "/dev/input";
|
||||||
|
|
||||||
@@ -320,9 +361,7 @@ bool _glfwInitJoysticksLinux(void)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close all opened joystick handles
|
void _glfwPlatformTerminateJoysticks(void)
|
||||||
//
|
|
||||||
void _glfwTerminateJoysticksLinux(void)
|
|
||||||
{
|
{
|
||||||
int jid;
|
int jid;
|
||||||
|
|
||||||
@@ -333,61 +372,16 @@ void _glfwTerminateJoysticksLinux(void)
|
|||||||
closeJoystick(js);
|
closeJoystick(js);
|
||||||
}
|
}
|
||||||
|
|
||||||
regfree(&_glfw.linjs.regex);
|
|
||||||
|
|
||||||
if (_glfw.linjs.inotify > 0)
|
if (_glfw.linjs.inotify > 0)
|
||||||
{
|
{
|
||||||
if (_glfw.linjs.watch > 0)
|
if (_glfw.linjs.watch > 0)
|
||||||
inotify_rm_watch(_glfw.linjs.inotify, _glfw.linjs.watch);
|
inotify_rm_watch(_glfw.linjs.inotify, _glfw.linjs.watch);
|
||||||
|
|
||||||
close(_glfw.linjs.inotify);
|
close(_glfw.linjs.inotify);
|
||||||
|
regfree(&_glfw.linjs.regex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _glfwDetectJoystickConnectionLinux(void)
|
|
||||||
{
|
|
||||||
|
|
||||||
if (_glfw.linjs.inotify <= 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
ssize_t offset = 0;
|
|
||||||
char buffer[16384];
|
|
||||||
const ssize_t size = read(_glfw.linjs.inotify, buffer, sizeof(buffer));
|
|
||||||
|
|
||||||
while (size > offset)
|
|
||||||
{
|
|
||||||
regmatch_t match;
|
|
||||||
const struct inotify_event* e = (struct inotify_event*) (buffer + offset);
|
|
||||||
|
|
||||||
offset += sizeof(struct inotify_event) + e->len;
|
|
||||||
|
|
||||||
if (regexec(&_glfw.linjs.regex, e->name, 1, &match, 0) != 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
char path[PATH_MAX];
|
|
||||||
snprintf(path, sizeof(path), "/dev/input/%s", e->name);
|
|
||||||
|
|
||||||
if (e->mask & (IN_CREATE | IN_ATTRIB))
|
|
||||||
openJoystickDevice(path);
|
|
||||||
else if (e->mask & IN_DELETE)
|
|
||||||
{
|
|
||||||
for (int jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++)
|
|
||||||
{
|
|
||||||
if (strcmp(_glfw.joysticks[jid].linjs.path, path) == 0)
|
|
||||||
{
|
|
||||||
closeJoystick(_glfw.joysticks + jid);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
|
||||||
////// GLFW platform API //////
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode UNUSED)
|
int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode UNUSED)
|
||||||
{
|
{
|
||||||
// Read all queued events (non-blocking)
|
// Read all queued events (non-blocking)
|
||||||
|
|||||||
3
glfw/linux_joystick.h
vendored
3
glfw/linux_joystick.h
vendored
@@ -55,7 +55,4 @@ typedef struct _GLFWlibraryLinux
|
|||||||
bool dropped;
|
bool dropped;
|
||||||
} _GLFWlibraryLinux;
|
} _GLFWlibraryLinux;
|
||||||
|
|
||||||
|
|
||||||
bool _glfwInitJoysticksLinux(void);
|
|
||||||
void _glfwTerminateJoysticksLinux(void);
|
|
||||||
void _glfwDetectJoystickConnectionLinux(void);
|
void _glfwDetectJoystickConnectionLinux(void);
|
||||||
|
|||||||
9
glfw/null_joystick.c
vendored
9
glfw/null_joystick.c
vendored
@@ -33,6 +33,15 @@
|
|||||||
////// GLFW platform API //////
|
////// GLFW platform API //////
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
bool _glfwPlatformInitJoysticks(void)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _glfwPlatformTerminateJoysticks(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
int _glfwPlatformPollJoystick(_GLFWjoystick* js UNUSED, int mode UNUSED)
|
int _glfwPlatformPollJoystick(_GLFWjoystick* js UNUSED, int mode UNUSED)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
10
glfw/null_monitor.c
vendored
10
glfw/null_monitor.c
vendored
@@ -70,8 +70,12 @@ void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor)
|
|||||||
_glfwFreeGammaArrays(&monitor->null.ramp);
|
_glfwFreeGammaArrays(&monitor->null.ramp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor UNUSED, int* xpos UNUSED, int* ypos UNUSED)
|
void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor UNUSED, int* xpos, int* ypos)
|
||||||
{
|
{
|
||||||
|
if (xpos)
|
||||||
|
*xpos = 0;
|
||||||
|
if (ypos)
|
||||||
|
*ypos = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor UNUSED,
|
void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor UNUSED,
|
||||||
@@ -90,9 +94,9 @@ void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor UNUSED,
|
|||||||
const GLFWvidmode mode = getVideoMode();
|
const GLFWvidmode mode = getVideoMode();
|
||||||
|
|
||||||
if (xpos)
|
if (xpos)
|
||||||
*xpos = 10;
|
*xpos = 0;
|
||||||
if (ypos)
|
if (ypos)
|
||||||
ypos = 0;
|
*ypos = 10;
|
||||||
if (width)
|
if (width)
|
||||||
*width = mode.width;
|
*width = mode.width;
|
||||||
if (height)
|
if (height)
|
||||||
|
|||||||
3
glfw/null_platform.h
vendored
3
glfw/null_platform.h
vendored
@@ -34,10 +34,7 @@
|
|||||||
#define _GLFW_PLATFORM_CONTEXT_STATE
|
#define _GLFW_PLATFORM_CONTEXT_STATE
|
||||||
#define _GLFW_PLATFORM_CURSOR_STATE
|
#define _GLFW_PLATFORM_CURSOR_STATE
|
||||||
#define _GLFW_PLATFORM_LIBRARY_CONTEXT_STATE
|
#define _GLFW_PLATFORM_LIBRARY_CONTEXT_STATE
|
||||||
#define _GLFW_EGL_CONTEXT_STATE
|
|
||||||
#define _GLFW_EGL_LIBRARY_CONTEXT_STATE
|
|
||||||
|
|
||||||
#include "osmesa_context.h"
|
|
||||||
#include "posix_time.h"
|
#include "posix_time.h"
|
||||||
#include "posix_thread.h"
|
#include "posix_thread.h"
|
||||||
#include "null_joystick.h"
|
#include "null_joystick.h"
|
||||||
|
|||||||
27
glfw/null_window.c
vendored
27
glfw/null_window.c
vendored
@@ -274,10 +274,25 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
|
|||||||
{
|
{
|
||||||
if (window->null.decorated && !window->monitor)
|
if (window->null.decorated && !window->monitor)
|
||||||
{
|
{
|
||||||
*left = 1;
|
if (left)
|
||||||
*top = 10;
|
*left = 1;
|
||||||
*right = 1;
|
if (top)
|
||||||
*bottom = 1;
|
*top = 10;
|
||||||
|
if (right)
|
||||||
|
*right = 1;
|
||||||
|
if (bottom)
|
||||||
|
*bottom = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (left)
|
||||||
|
*left = 0;
|
||||||
|
if (top)
|
||||||
|
*top = 0;
|
||||||
|
if (right)
|
||||||
|
*right = 0;
|
||||||
|
if (bottom)
|
||||||
|
*bottom = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,6 +387,10 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled)
|
|||||||
window->null.floating = enabled;
|
window->null.floating = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window UNUSED, bool enabled UNUSED)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window)
|
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window)
|
||||||
{
|
{
|
||||||
return window->null.opacity;
|
return window->null.opacity;
|
||||||
|
|||||||
4
glfw/osmesa_context.h
vendored
4
glfw/osmesa_context.h
vendored
@@ -54,10 +54,6 @@ typedef GLFWglproc (GLAPIENTRY * PFN_OSMesaGetProcAddress)(const char*);
|
|||||||
#define OSMesaGetDepthBuffer _glfw.osmesa.GetDepthBuffer
|
#define OSMesaGetDepthBuffer _glfw.osmesa.GetDepthBuffer
|
||||||
#define OSMesaGetProcAddress _glfw.osmesa.GetProcAddress
|
#define OSMesaGetProcAddress _glfw.osmesa.GetProcAddress
|
||||||
|
|
||||||
#define _GLFW_OSMESA_CONTEXT_STATE _GLFWcontextOSMesa osmesa
|
|
||||||
#define _GLFW_OSMESA_LIBRARY_CONTEXT_STATE _GLFWlibraryOSMesa osmesa
|
|
||||||
|
|
||||||
|
|
||||||
// OSMesa-specific per-context data
|
// OSMesa-specific per-context data
|
||||||
//
|
//
|
||||||
typedef struct _GLFWcontextOSMesa
|
typedef struct _GLFWcontextOSMesa
|
||||||
|
|||||||
35
glfw/window.c
vendored
35
glfw/window.c
vendored
@@ -240,13 +240,14 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
|
|||||||
window->videoMode.blueBits = fbconfig.blueBits;
|
window->videoMode.blueBits = fbconfig.blueBits;
|
||||||
window->videoMode.refreshRate = _glfw.hints.refreshRate;
|
window->videoMode.refreshRate = _glfw.hints.refreshRate;
|
||||||
|
|
||||||
window->monitor = (_GLFWmonitor*) monitor;
|
window->monitor = (_GLFWmonitor*) monitor;
|
||||||
window->resizable = wndconfig.resizable;
|
window->resizable = wndconfig.resizable;
|
||||||
window->decorated = wndconfig.decorated;
|
window->decorated = wndconfig.decorated;
|
||||||
window->autoIconify = wndconfig.autoIconify;
|
window->autoIconify = wndconfig.autoIconify;
|
||||||
window->floating = wndconfig.floating;
|
window->floating = wndconfig.floating;
|
||||||
window->focusOnShow = wndconfig.focusOnShow;
|
window->focusOnShow = wndconfig.focusOnShow;
|
||||||
window->cursorMode = GLFW_CURSOR_NORMAL;
|
window->mousePassthrough = wndconfig.mousePassthrough;
|
||||||
|
window->cursorMode = GLFW_CURSOR_NORMAL;
|
||||||
|
|
||||||
window->minwidth = GLFW_DONT_CARE;
|
window->minwidth = GLFW_DONT_CARE;
|
||||||
window->minheight = GLFW_DONT_CARE;
|
window->minheight = GLFW_DONT_CARE;
|
||||||
@@ -273,6 +274,9 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (wndconfig.mousePassthrough)
|
||||||
|
_glfwPlatformSetWindowMousePassthrough(window, true);
|
||||||
|
|
||||||
if (window->monitor)
|
if (window->monitor)
|
||||||
{
|
{
|
||||||
if (wndconfig.centerCursor)
|
if (wndconfig.centerCursor)
|
||||||
@@ -422,6 +426,9 @@ GLFWAPI void glfwWindowHint(int hint, int value)
|
|||||||
case GLFW_FOCUS_ON_SHOW:
|
case GLFW_FOCUS_ON_SHOW:
|
||||||
_glfw.hints.window.focusOnShow = value ? true : false;
|
_glfw.hints.window.focusOnShow = value ? true : false;
|
||||||
return;
|
return;
|
||||||
|
case GLFW_MOUSE_PASSTHROUGH:
|
||||||
|
_glfw.hints.window.mousePassthrough = value ? true : false;
|
||||||
|
return;
|
||||||
case GLFW_CLIENT_API:
|
case GLFW_CLIENT_API:
|
||||||
_glfw.hints.context.client = value;
|
_glfw.hints.context.client = value;
|
||||||
return;
|
return;
|
||||||
@@ -440,7 +447,7 @@ GLFWAPI void glfwWindowHint(int hint, int value)
|
|||||||
case GLFW_OPENGL_FORWARD_COMPAT:
|
case GLFW_OPENGL_FORWARD_COMPAT:
|
||||||
_glfw.hints.context.forward = value ? true : false;
|
_glfw.hints.context.forward = value ? true : false;
|
||||||
return;
|
return;
|
||||||
case GLFW_OPENGL_DEBUG_CONTEXT:
|
case GLFW_CONTEXT_DEBUG:
|
||||||
_glfw.hints.context.debug = value ? true : false;
|
_glfw.hints.context.debug = value ? true : false;
|
||||||
return;
|
return;
|
||||||
case GLFW_CONTEXT_NO_ERROR:
|
case GLFW_CONTEXT_NO_ERROR:
|
||||||
@@ -905,6 +912,8 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* handle, int attrib)
|
|||||||
return _glfwPlatformWindowHovered(window);
|
return _glfwPlatformWindowHovered(window);
|
||||||
case GLFW_FOCUS_ON_SHOW:
|
case GLFW_FOCUS_ON_SHOW:
|
||||||
return window->focusOnShow;
|
return window->focusOnShow;
|
||||||
|
case GLFW_MOUSE_PASSTHROUGH:
|
||||||
|
return window->mousePassthrough;
|
||||||
case GLFW_TRANSPARENT_FRAMEBUFFER:
|
case GLFW_TRANSPARENT_FRAMEBUFFER:
|
||||||
return _glfwPlatformFramebufferTransparent(window);
|
return _glfwPlatformFramebufferTransparent(window);
|
||||||
case GLFW_OCCLUDED:
|
case GLFW_OCCLUDED:
|
||||||
@@ -931,7 +940,7 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* handle, int attrib)
|
|||||||
return window->context.robustness;
|
return window->context.robustness;
|
||||||
case GLFW_OPENGL_FORWARD_COMPAT:
|
case GLFW_OPENGL_FORWARD_COMPAT:
|
||||||
return window->context.forward;
|
return window->context.forward;
|
||||||
case GLFW_OPENGL_DEBUG_CONTEXT:
|
case GLFW_CONTEXT_DEBUG:
|
||||||
return window->context.debug;
|
return window->context.debug;
|
||||||
case GLFW_OPENGL_PROFILE:
|
case GLFW_OPENGL_PROFILE:
|
||||||
return window->context.profile;
|
return window->context.profile;
|
||||||
@@ -985,6 +994,14 @@ GLFWAPI void glfwSetWindowAttrib(GLFWwindow* handle, int attrib, int value)
|
|||||||
}
|
}
|
||||||
else if (attrib == GLFW_FOCUS_ON_SHOW)
|
else if (attrib == GLFW_FOCUS_ON_SHOW)
|
||||||
window->focusOnShow = value;
|
window->focusOnShow = value;
|
||||||
|
else if (attrib == GLFW_MOUSE_PASSTHROUGH)
|
||||||
|
{
|
||||||
|
if (window->mousePassthrough == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
window->mousePassthrough = value;
|
||||||
|
_glfwPlatformSetWindowMousePassthrough(window, value);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
_glfwInputError(GLFW_INVALID_ENUM, "Invalid window attribute 0x%08X", attrib);
|
_glfwInputError(GLFW_INVALID_ENUM, "Invalid window attribute 0x%08X", attrib);
|
||||||
}
|
}
|
||||||
|
|||||||
181
glfw/wl_cursors.c
vendored
181
glfw/wl_cursors.c
vendored
@@ -1,7 +1,5 @@
|
|||||||
// Future devs supporting whatever Wayland protocol stabilizes for cursor selection: see _themeAdd.
|
// Future devs supporting whatever Wayland protocol stabilizes for cursor selection: see _themeAdd.
|
||||||
|
|
||||||
#include "wl_cursors.h"
|
|
||||||
|
|
||||||
#include "internal.h"
|
#include "internal.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
@@ -10,155 +8,58 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
typedef struct {
|
static int
|
||||||
struct wl_cursor_theme *theme;
|
pixels_from_scale(int scale) {
|
||||||
int px;
|
static bool queried_env = false;
|
||||||
int refcount;
|
static int factor = 32;
|
||||||
} _themeData;
|
if (!queried_env) {
|
||||||
|
const char *env = getenv("XCURSOR_SIZE");
|
||||||
struct _wlCursorThemeManager {
|
if (env) {
|
||||||
size_t count;
|
const int retval = atoi(env);
|
||||||
/** Pointer to the head of an unsorted array of themes with no sentinel.
|
if (retval > 0 && retval < 2048) factor = retval;
|
||||||
*
|
|
||||||
* The lack of sort (and thus forcing a linear search) is intentional;
|
|
||||||
* in most cases, users are likely to have 1-2 different cursor sizes loaded.
|
|
||||||
* For those cases, we get no benefit from sorting and added constant overheads.
|
|
||||||
*
|
|
||||||
* Don't change this to a flexible array member because that complicates growing/shrinking.
|
|
||||||
*/
|
|
||||||
_themeData *themes;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void
|
|
||||||
_themeInit(_themeData *dest, const char *name, int px) {
|
|
||||||
dest->px = px;
|
|
||||||
dest->refcount = 1;
|
|
||||||
if (_glfw.wl.shm) {
|
|
||||||
dest->theme = wl_cursor_theme_load(name, px, _glfw.wl.shm);
|
|
||||||
if(!dest->theme) {
|
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
|
||||||
"Wayland: Unable to load cursor theme");
|
|
||||||
}
|
}
|
||||||
} else {
|
queried_env = true;
|
||||||
dest->theme = NULL;
|
|
||||||
}
|
}
|
||||||
|
return factor * scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct wl_cursor_theme*
|
|
||||||
_themeAdd(int px, _wlCursorThemeManager *manager) {
|
|
||||||
++manager->count;
|
|
||||||
_themeData *temp = realloc(manager->themes, sizeof(_themeData)*manager->count);
|
|
||||||
if (!temp) {
|
|
||||||
_glfwInputError(GLFW_OUT_OF_MEMORY,
|
|
||||||
"OOM during cursor theme management.");
|
|
||||||
return NULL;
|
|
||||||
} else {
|
|
||||||
manager->themes = temp;
|
|
||||||
_themeInit(manager->themes + manager->count-1,
|
|
||||||
getenv("XCURSOR_THEME"),
|
|
||||||
px);
|
|
||||||
return manager->themes[manager->count-1].theme;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//WARNING: No input safety checks.
|
struct wl_cursor_theme*
|
||||||
static inline void _themeInc(_themeData
|
glfw_wlc_theme_for_scale(int scale) {
|
||||||
*theme) {
|
GLFWWLCursorThemes *t = &_glfw.wl.cursor_themes;
|
||||||
++(theme->refcount);
|
for (size_t i = 0; i < t->count; i++) {
|
||||||
}
|
if (t->themes[i].scale == scale) return t->themes[i].theme;
|
||||||
|
}
|
||||||
|
|
||||||
// WARNING: No input safety checks.
|
if (t->count >= t->capacity) {
|
||||||
// In particular, doesn't check if theme is actually managed by the manager.
|
t->themes = realloc(t->themes, sizeof(GLFWWLCursorTheme) * (t->count + 16));
|
||||||
static void
|
if (!t->themes) {
|
||||||
_themeDec(_themeData *theme, _wlCursorThemeManager *manager) {
|
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Out of memory allocating space for cursor themes");
|
||||||
if (--(theme->refcount) == 0) {
|
return NULL;
|
||||||
wl_cursor_theme_destroy(theme->theme);
|
|
||||||
if (--(manager->count) > 0) {
|
|
||||||
const _themeData *last_theme = (manager->themes)+(manager->count);
|
|
||||||
*theme = *last_theme;
|
|
||||||
_themeData *temp = realloc(manager->themes, (manager->count)*sizeof(_themeData));
|
|
||||||
// We're shrinking here, so it's not catastrophic if realloc fails.
|
|
||||||
if (temp) manager->themes = temp;
|
|
||||||
} else {
|
|
||||||
free(manager->themes);
|
|
||||||
manager->themes = NULL;
|
|
||||||
}
|
}
|
||||||
|
t->capacity = t->count + 16;
|
||||||
}
|
}
|
||||||
}
|
struct wl_cursor_theme *ans = wl_cursor_theme_load(getenv("XCURSOR_THEME"), pixels_from_scale(scale), _glfw.wl.shm);
|
||||||
|
if (!ans) {
|
||||||
static _wlCursorThemeManager _default = {0};
|
_glfwInputError(
|
||||||
|
GLFW_PLATFORM_ERROR, "Wayland: wl_cursor_theme_load failed at scale: %d pixels: %d",
|
||||||
_wlCursorThemeManager*
|
scale, pixels_from_scale(scale)
|
||||||
_wlCursorThemeManagerDefault() {
|
);
|
||||||
return &_default;
|
return NULL;
|
||||||
|
}
|
||||||
|
GLFWWLCursorTheme *theme = t->themes + t->count++;
|
||||||
|
theme->scale = scale;
|
||||||
|
theme->theme = ans;
|
||||||
|
return ans;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
_wlCursorThemeManagerDestroy(_wlCursorThemeManager *manager) {
|
glfw_wlc_destroy(void) {
|
||||||
if (manager) {
|
GLFWWLCursorThemes *t = &_glfw.wl.cursor_themes;
|
||||||
for (size_t i = 0; i < manager->count; ++i) {
|
|
||||||
wl_cursor_theme_destroy(manager->themes[i].theme);
|
|
||||||
}
|
|
||||||
free(manager->themes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct wl_cursor_theme*
|
for (size_t i = 0; i < t->count; i++) {
|
||||||
_wlCursorThemeManagerGet(_wlCursorThemeManager *manager, int px) {
|
wl_cursor_theme_destroy(t->themes[i].theme);
|
||||||
_themeData *themedata = NULL;
|
|
||||||
for (size_t i = 0; i < manager->count; ++i) {
|
|
||||||
_themeData *temp = manager->themes+i;
|
|
||||||
if (temp->px == px) {
|
|
||||||
themedata = temp;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (themedata != NULL) {
|
free(t->themes);
|
||||||
_themeInc(themedata);
|
t->themes = NULL; t->capacity = 0; t->count = 0;
|
||||||
return themedata->theme;
|
|
||||||
}
|
|
||||||
return _themeAdd(px, manager);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct wl_cursor_theme*
|
|
||||||
_wlCursorThemeManage(_wlCursorThemeManager *manager, struct wl_cursor_theme *theme, int px) {
|
|
||||||
//WARNING: Multiple returns.
|
|
||||||
if (manager == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
if (theme != NULL) {
|
|
||||||
// Search for the provided theme in the manager.
|
|
||||||
_themeData *themedata = NULL;
|
|
||||||
for (size_t i = 0; i < manager->count; ++i) {
|
|
||||||
_themeData *temp = manager->themes+i;
|
|
||||||
if (temp->theme == theme) {
|
|
||||||
themedata = temp;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (themedata != NULL) {
|
|
||||||
// Search succeeded. Check if we can avoid unnecessary operations.
|
|
||||||
if (themedata->px == px) return theme;
|
|
||||||
_themeDec(themedata, manager);
|
|
||||||
} else {
|
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
|
||||||
"Wayland internal: managed theme isn't in the provided manager");
|
|
||||||
return theme;
|
|
||||||
//^ This is probably the sanest behavior for this situation: do nothing.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return px > 0 ? _wlCursorThemeManagerGet(manager, px) : NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
_wlCursorPxFromScale(int scale) {
|
|
||||||
const char *envStr = getenv("XCURSOR_SIZE");
|
|
||||||
if(envStr != NULL) {
|
|
||||||
const int retval = atoi(envStr);
|
|
||||||
//^ atoi here is fine since 0 is an invalid value.
|
|
||||||
if(retval > 0 && retval <= INT_MAX/scale) {
|
|
||||||
return retval*scale;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 32*scale;
|
|
||||||
}
|
}
|
||||||
|
|||||||
32
glfw/wl_cursors.h
vendored
32
glfw/wl_cursors.h
vendored
@@ -2,29 +2,17 @@
|
|||||||
|
|
||||||
#include <wayland-cursor.h>
|
#include <wayland-cursor.h>
|
||||||
|
|
||||||
typedef struct _wlCursorThemeManager _wlCursorThemeManager;
|
typedef struct {
|
||||||
|
struct wl_cursor_theme *theme;
|
||||||
|
int scale;
|
||||||
|
} GLFWWLCursorTheme;
|
||||||
|
|
||||||
/** Returns a pointer to a wlCursorThemeManagerInstance.
|
|
||||||
* Repeatedly calling this function will return the same instance.
|
|
||||||
*
|
|
||||||
* The retrieved instance must be destroyed with _wlCursorThemeManagerDestroy.
|
|
||||||
*/
|
|
||||||
_wlCursorThemeManager* _wlCursorThemeManagerDefault(void);
|
|
||||||
|
|
||||||
/** Set a wl_cursor_theme pointer variable to a pointer to a managed cursor theme.
|
typedef struct {
|
||||||
* Pass the desired px as the third argument.
|
GLFWWLCursorTheme *themes;
|
||||||
* Returns a pointer to a managed theme, or NULL if the desired px is 0 or an error occurs.
|
size_t count, capacity;
|
||||||
*
|
} GLFWWLCursorThemes;
|
||||||
* The passed theme pointer must either be NULL or a pointer to a theme managed by the passed manager.
|
|
||||||
* The provided pointer may be invalidated if it's non-NULL.
|
|
||||||
*/
|
|
||||||
struct wl_cursor_theme*
|
|
||||||
_wlCursorThemeManage(_wlCursorThemeManager*, struct wl_cursor_theme*, int);
|
|
||||||
|
|
||||||
void _wlCursorThemeManagerDestroy(_wlCursorThemeManager*);
|
|
||||||
|
|
||||||
/** Helper method to determine the appropriate size in pixels for a given scale.
|
struct wl_cursor_theme* glfw_wlc_theme_for_scale(int scale);
|
||||||
*
|
void glfw_wlc_destroy(void);
|
||||||
* Reads XCURSOR_SIZE if it's set and is valid, else defaults to 32*scale.
|
|
||||||
*/
|
|
||||||
int _wlCursorPxFromScale(int);
|
|
||||||
|
|||||||
32
glfw/wl_init.c
vendored
32
glfw/wl_init.c
vendored
@@ -148,12 +148,9 @@ static void setCursor(GLFWCursorShape shape, _GLFWwindow* window)
|
|||||||
struct wl_surface* surface = _glfw.wl.cursorSurface;
|
struct wl_surface* surface = _glfw.wl.cursorSurface;
|
||||||
const int scale = window->wl.scale;
|
const int scale = window->wl.scale;
|
||||||
|
|
||||||
window->wl.cursorTheme = _wlCursorThemeManage(
|
struct wl_cursor_theme *theme = glfw_wlc_theme_for_scale(scale);
|
||||||
_glfw.wl.cursorThemeManager,
|
if (!theme) return;
|
||||||
window->wl.cursorTheme,
|
cursor = _glfwLoadCursor(shape, theme);
|
||||||
_wlCursorPxFromScale(scale)
|
|
||||||
);
|
|
||||||
cursor = _glfwLoadCursor(shape, window->wl.cursorTheme);
|
|
||||||
if (!cursor) return;
|
if (!cursor) return;
|
||||||
// TODO: handle animated cursors too.
|
// TODO: handle animated cursors too.
|
||||||
image = cursor->images[0];
|
image = cursor->images[0];
|
||||||
@@ -337,9 +334,9 @@ static void pointerHandleAxis(void* data UNUSED,
|
|||||||
axis == WL_POINTER_AXIS_VERTICAL_SCROLL);
|
axis == WL_POINTER_AXIS_VERTICAL_SCROLL);
|
||||||
|
|
||||||
if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL)
|
if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL)
|
||||||
x = wl_fixed_to_double(value) * -1;
|
x = -wl_fixed_to_double(value);
|
||||||
else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
|
else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
|
||||||
y = wl_fixed_to_double(value) * -1;
|
y = -wl_fixed_to_double(value);
|
||||||
|
|
||||||
_glfwInputScroll(window, x, y, 1, _glfw.wl.xkb.states.modifiers);
|
_glfwInputScroll(window, x, y, 1, _glfw.wl.xkb.states.modifiers);
|
||||||
}
|
}
|
||||||
@@ -771,13 +768,6 @@ int _glfwPlatformInit(void)
|
|||||||
// Sync so we got all initial output events
|
// Sync so we got all initial output events
|
||||||
wl_display_roundtrip(_glfw.wl.display);
|
wl_display_roundtrip(_glfw.wl.display);
|
||||||
|
|
||||||
#ifdef __linux__
|
|
||||||
if (_glfw.hints.init.enableJoysticks) {
|
|
||||||
if (!_glfwInitJoysticksLinux())
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!_glfw.wl.wmBase)
|
if (!_glfw.wl.wmBase)
|
||||||
{
|
{
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||||
@@ -787,13 +777,14 @@ int _glfwPlatformInit(void)
|
|||||||
|
|
||||||
if (_glfw.wl.shm)
|
if (_glfw.wl.shm)
|
||||||
{
|
{
|
||||||
_glfw.wl.cursorThemeManager = _wlCursorThemeManagerDefault();
|
|
||||||
_glfw.wl.cursorSurface =
|
_glfw.wl.cursorSurface =
|
||||||
wl_compositor_create_surface(_glfw.wl.compositor);
|
wl_compositor_create_surface(_glfw.wl.compositor);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_glfw.wl.cursorThemeManager = NULL;
|
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||||
|
"Wayland: Failed to find Wayland SHM");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -801,9 +792,6 @@ int _glfwPlatformInit(void)
|
|||||||
|
|
||||||
void _glfwPlatformTerminate(void)
|
void _glfwPlatformTerminate(void)
|
||||||
{
|
{
|
||||||
#ifdef __linux__
|
|
||||||
_glfwTerminateJoysticksLinux();
|
|
||||||
#endif
|
|
||||||
_glfwTerminateEGL();
|
_glfwTerminateEGL();
|
||||||
if (_glfw.wl.egl.handle)
|
if (_glfw.wl.egl.handle)
|
||||||
{
|
{
|
||||||
@@ -814,6 +802,7 @@ void _glfwPlatformTerminate(void)
|
|||||||
glfw_xkb_release(&_glfw.wl.xkb);
|
glfw_xkb_release(&_glfw.wl.xkb);
|
||||||
glfw_dbus_terminate(&_glfw.wl.dbus);
|
glfw_dbus_terminate(&_glfw.wl.dbus);
|
||||||
|
|
||||||
|
glfw_wlc_destroy();
|
||||||
if (_glfw.wl.cursor.handle)
|
if (_glfw.wl.cursor.handle)
|
||||||
{
|
{
|
||||||
_glfw_dlclose(_glfw.wl.cursor.handle);
|
_glfw_dlclose(_glfw.wl.cursor.handle);
|
||||||
@@ -822,9 +811,6 @@ void _glfwPlatformTerminate(void)
|
|||||||
|
|
||||||
if (_glfw.wl.cursorSurface)
|
if (_glfw.wl.cursorSurface)
|
||||||
wl_surface_destroy(_glfw.wl.cursorSurface);
|
wl_surface_destroy(_glfw.wl.cursorSurface);
|
||||||
if (_glfw.wl.cursorThemeManager) {
|
|
||||||
_wlCursorThemeManagerDestroy(_glfw.wl.cursorThemeManager);
|
|
||||||
}
|
|
||||||
if (_glfw.wl.subcompositor)
|
if (_glfw.wl.subcompositor)
|
||||||
wl_subcompositor_destroy(_glfw.wl.subcompositor);
|
wl_subcompositor_destroy(_glfw.wl.subcompositor);
|
||||||
if (_glfw.wl.compositor)
|
if (_glfw.wl.compositor)
|
||||||
|
|||||||
11
glfw/wl_platform.h
vendored
11
glfw/wl_platform.h
vendored
@@ -50,8 +50,6 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
|
|||||||
#endif
|
#endif
|
||||||
#include "backend_utils.h"
|
#include "backend_utils.h"
|
||||||
#include "xkb_glfw.h"
|
#include "xkb_glfw.h"
|
||||||
#include "egl_context.h"
|
|
||||||
#include "osmesa_context.h"
|
|
||||||
#include "wl_cursors.h"
|
#include "wl_cursors.h"
|
||||||
|
|
||||||
#include "wayland-xdg-shell-client-protocol.h"
|
#include "wayland-xdg-shell-client-protocol.h"
|
||||||
@@ -66,9 +64,6 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
|
|||||||
#define _glfw_dlclose(handle) dlclose(handle)
|
#define _glfw_dlclose(handle) dlclose(handle)
|
||||||
#define _glfw_dlsym(handle, name) dlsym(handle, name)
|
#define _glfw_dlsym(handle, name) dlsym(handle, name)
|
||||||
|
|
||||||
#define _GLFW_EGL_NATIVE_WINDOW ((EGLNativeWindowType) window->wl.native)
|
|
||||||
#define _GLFW_EGL_NATIVE_DISPLAY ((EGLNativeDisplayType) _glfw.wl.display)
|
|
||||||
|
|
||||||
#define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowWayland wl
|
#define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowWayland wl
|
||||||
#define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryWayland wl
|
#define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryWayland wl
|
||||||
#define _GLFW_PLATFORM_MONITOR_STATE _GLFWmonitorWayland wl
|
#define _GLFW_PLATFORM_MONITOR_STATE _GLFWmonitorWayland wl
|
||||||
@@ -137,7 +132,6 @@ typedef struct _GLFWwindowWayland
|
|||||||
|
|
||||||
_GLFWcursor* currentCursor;
|
_GLFWcursor* currentCursor;
|
||||||
double cursorPosX, cursorPosY;
|
double cursorPosX, cursorPosY;
|
||||||
struct wl_cursor_theme* cursorTheme;
|
|
||||||
|
|
||||||
char* title;
|
char* title;
|
||||||
char appId[256];
|
char appId[256];
|
||||||
@@ -267,8 +261,7 @@ typedef struct _GLFWlibraryWayland
|
|||||||
size_t dataOffersCounter;
|
size_t dataOffersCounter;
|
||||||
_GLFWWaylandDataOffer dataOffers[8];
|
_GLFWWaylandDataOffer dataOffers[8];
|
||||||
char* primarySelectionString;
|
char* primarySelectionString;
|
||||||
|
GLFWWLCursorThemes cursor_themes;
|
||||||
_wlCursorThemeManager* cursorThemeManager;
|
|
||||||
} _GLFWlibraryWayland;
|
} _GLFWlibraryWayland;
|
||||||
|
|
||||||
// Wayland-specific per-monitor data
|
// Wayland-specific per-monitor data
|
||||||
@@ -292,7 +285,7 @@ typedef struct _GLFWcursorWayland
|
|||||||
struct wl_buffer* buffer;
|
struct wl_buffer* buffer;
|
||||||
int width, height;
|
int width, height;
|
||||||
int xhot, yhot;
|
int xhot, yhot;
|
||||||
int currentImage;
|
unsigned int currentImage;
|
||||||
/** The scale of the cursor, or 0 if the cursor should be loaded late, or -1 if the cursor variable itself is unused. */
|
/** The scale of the cursor, or 0 if the cursor should be loaded late, or -1 if the cursor variable itself is unused. */
|
||||||
int scale;
|
int scale;
|
||||||
/** Cursor shape stored to allow late cursor loading in setCursorImage. */
|
/** Cursor shape stored to allow late cursor loading in setCursorImage. */
|
||||||
|
|||||||
80
glfw/wl_window.c
vendored
80
glfw/wl_window.c
vendored
@@ -41,11 +41,6 @@
|
|||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
|
|
||||||
static void
|
|
||||||
load_cursor_theme(_GLFWwindow* window) {
|
|
||||||
window->wl.cursorTheme = _wlCursorThemeManage(
|
|
||||||
_glfw.wl.cursorThemeManager, window->wl.cursorTheme, _wlCursorPxFromScale(window->wl.scale));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
setCursorImage(_GLFWwindow* window)
|
setCursorImage(_GLFWwindow* window)
|
||||||
@@ -63,18 +58,23 @@ setCursorImage(_GLFWwindow* window)
|
|||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
if (cursorWayland->scale != scale) {
|
if (cursorWayland->scale != scale) {
|
||||||
if (!window->wl.cursorTheme) load_cursor_theme(window);
|
struct wl_cursor *newCursor = NULL;
|
||||||
struct wl_cursor *newCursor = _glfwLoadCursor(cursorWayland->shape, window->wl.cursorTheme);
|
struct wl_cursor_theme *theme = glfw_wlc_theme_for_scale(scale);
|
||||||
|
if (theme) newCursor = _glfwLoadCursor(cursorWayland->shape, theme);
|
||||||
if (newCursor != NULL) {
|
if (newCursor != NULL) {
|
||||||
cursorWayland->cursor = newCursor;
|
cursorWayland->cursor = newCursor;
|
||||||
cursorWayland->scale = scale;
|
cursorWayland->scale = scale;
|
||||||
|
cursorWayland->currentImage = 0;
|
||||||
} else {
|
} else {
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: late cursor load failed; proceeding with existing cursor");
|
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: late cursor load failed; proceeding with existing cursor");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!cursorWayland->cursor)
|
if (!cursorWayland->cursor || !cursorWayland->cursor->image_count)
|
||||||
return;
|
return;
|
||||||
|
if (cursorWayland->currentImage >= cursorWayland->cursor->image_count) cursorWayland->currentImage = 0;
|
||||||
image = cursorWayland->cursor->images[cursorWayland->currentImage];
|
image = cursorWayland->cursor->images[cursorWayland->currentImage];
|
||||||
|
if (!image) image = cursorWayland->cursor->images[0];
|
||||||
|
if (!image) return;
|
||||||
buffer = wl_cursor_image_get_buffer(image);
|
buffer = wl_cursor_image_get_buffer(image);
|
||||||
if (image->delay && window->cursor) {
|
if (image->delay && window->cursor) {
|
||||||
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, ms_to_monotonic_t(image->delay));
|
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, ms_to_monotonic_t(image->delay));
|
||||||
@@ -132,7 +132,6 @@ static bool checkScaleChange(_GLFWwindow* window)
|
|||||||
{
|
{
|
||||||
window->wl.scale = scale;
|
window->wl.scale = scale;
|
||||||
wl_surface_set_buffer_scale(window->wl.surface, scale);
|
wl_surface_set_buffer_scale(window->wl.surface, scale);
|
||||||
load_cursor_theme(window);
|
|
||||||
setCursorImage(window);
|
setCursorImage(window);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -764,14 +763,18 @@ animateCursorImage(id_type timer_id UNUSED, void *data UNUSED) {
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
abortOnFatalError(int last_error) {
|
abortOnFatalError(int last_error) {
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: fatal display error: %s", strerror(last_error));
|
static bool abort_called = false;
|
||||||
if (_glfw.callbacks.application_close) _glfw.callbacks.application_close(1);
|
if (!abort_called) {
|
||||||
else {
|
abort_called = true;
|
||||||
_GLFWwindow* window = _glfw.windowListHead;
|
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: fatal display error: %s", strerror(last_error));
|
||||||
while (window)
|
if (_glfw.callbacks.application_close) _glfw.callbacks.application_close(1);
|
||||||
{
|
else {
|
||||||
_glfwInputWindowCloseRequest(window);
|
_GLFWwindow* window = _glfw.windowListHead;
|
||||||
window = window->next;
|
while (window)
|
||||||
|
{
|
||||||
|
_glfwInputWindowCloseRequest(window);
|
||||||
|
window = window->next;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ensure the tick callback is called
|
// ensure the tick callback is called
|
||||||
@@ -937,12 +940,6 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
|||||||
|
|
||||||
void _glfwPlatformDestroyWindow(_GLFWwindow* window)
|
void _glfwPlatformDestroyWindow(_GLFWwindow* window)
|
||||||
{
|
{
|
||||||
if (window->wl.cursorTheme) {
|
|
||||||
_wlCursorThemeManage(_glfw.wl.cursorThemeManager,
|
|
||||||
window->wl.cursorTheme,
|
|
||||||
0);
|
|
||||||
window->wl.cursorTheme = NULL;
|
|
||||||
}
|
|
||||||
if (window == _glfw.wl.pointerFocus)
|
if (window == _glfw.wl.pointerFocus)
|
||||||
{
|
{
|
||||||
_glfw.wl.pointerFocus = NULL;
|
_glfw.wl.pointerFocus = NULL;
|
||||||
@@ -1075,8 +1072,10 @@ void _glfwPlatformGetFramebufferSize(_GLFWwindow* window,
|
|||||||
int* width, int* height)
|
int* width, int* height)
|
||||||
{
|
{
|
||||||
_glfwPlatformGetWindowSize(window, width, height);
|
_glfwPlatformGetWindowSize(window, width, height);
|
||||||
*width *= window->wl.scale;
|
if (width)
|
||||||
*height *= window->wl.scale;
|
*width *= window->wl.scale;
|
||||||
|
if (height)
|
||||||
|
*height *= window->wl.scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
|
void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
|
||||||
@@ -1263,6 +1262,19 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window UNUSED, bool enabled UNU
|
|||||||
"Wayland: Window attribute setting not implemented yet");
|
"Wayland: Window attribute setting not implemented yet");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled)
|
||||||
|
{
|
||||||
|
if (enabled)
|
||||||
|
{
|
||||||
|
struct wl_region* region = wl_compositor_create_region(_glfw.wl.compositor);
|
||||||
|
wl_surface_set_input_region(window->wl.surface, region);
|
||||||
|
wl_region_destroy(region);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
wl_surface_set_input_region(window->wl.surface, 0);
|
||||||
|
wl_surface_commit(window->wl.surface);
|
||||||
|
}
|
||||||
|
|
||||||
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window UNUSED)
|
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window UNUSED)
|
||||||
{
|
{
|
||||||
return 1.f;
|
return 1.f;
|
||||||
@@ -2008,6 +2020,24 @@ const char* _glfwPlatformGetPrimarySelectionString(void)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs UNUSED)
|
||||||
|
{
|
||||||
|
if (_glfw.egl.EXT_platform_base && _glfw.egl.EXT_platform_wayland)
|
||||||
|
return EGL_PLATFORM_WAYLAND_EXT;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void)
|
||||||
|
{
|
||||||
|
return _glfw.wl.display;
|
||||||
|
}
|
||||||
|
|
||||||
|
EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window)
|
||||||
|
{
|
||||||
|
return window->wl.native;
|
||||||
|
}
|
||||||
|
|
||||||
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
|
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
|
||||||
{
|
{
|
||||||
if (!_glfw.vk.KHR_surface || !_glfw.vk.KHR_wayland_surface)
|
if (!_glfw.vk.KHR_surface || !_glfw.vk.KHR_wayland_surface)
|
||||||
|
|||||||
36
glfw/x11_init.c
vendored
36
glfw/x11_init.c
vendored
@@ -327,6 +327,30 @@ static bool initExtensions(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(__CYGWIN__)
|
||||||
|
_glfw.x11.xshape.handle = _glfw_dlopen("libXext-6.so");
|
||||||
|
#else
|
||||||
|
_glfw.x11.xshape.handle = _glfw_dlopen("libXext.so.6");
|
||||||
|
#endif
|
||||||
|
if (_glfw.x11.xshape.handle)
|
||||||
|
{
|
||||||
|
glfw_dlsym(_glfw.x11.xshape.QueryExtension, _glfw.x11.xshape.handle, "XShapeQueryExtension");
|
||||||
|
glfw_dlsym(_glfw.x11.xshape.ShapeCombineRegion, _glfw.x11.xshape.handle, "XShapeCombineRegion");
|
||||||
|
glfw_dlsym(_glfw.x11.xshape.QueryVersion, _glfw.x11.xshape.handle, "XShapeQueryVersion");
|
||||||
|
|
||||||
|
if (XShapeQueryExtension(_glfw.x11.display,
|
||||||
|
&_glfw.x11.xshape.errorBase,
|
||||||
|
&_glfw.x11.xshape.eventBase))
|
||||||
|
{
|
||||||
|
if (XShapeQueryVersion(_glfw.x11.display,
|
||||||
|
&_glfw.x11.xshape.major,
|
||||||
|
&_glfw.x11.xshape.minor))
|
||||||
|
{
|
||||||
|
_glfw.x11.xshape.available = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_glfw.x11.xkb.major = 1;
|
_glfw.x11.xkb.major = 1;
|
||||||
_glfw.x11.xkb.minor = 0;
|
_glfw.x11.xkb.minor = 0;
|
||||||
_glfw.x11.xkb.available = XkbQueryExtension(_glfw.x11.display,
|
_glfw.x11.xkb.available = XkbQueryExtension(_glfw.x11.display,
|
||||||
@@ -629,15 +653,6 @@ int _glfwPlatformInit(void)
|
|||||||
_glfw.x11.helperWindowHandle = createHelperWindow();
|
_glfw.x11.helperWindowHandle = createHelperWindow();
|
||||||
_glfw.x11.hiddenCursorHandle = createHiddenCursor();
|
_glfw.x11.hiddenCursorHandle = createHiddenCursor();
|
||||||
|
|
||||||
#if defined(__linux__)
|
|
||||||
if (_glfw.hints.init.enableJoysticks) {
|
|
||||||
if (!_glfwInitJoysticksLinux())
|
|
||||||
return false;
|
|
||||||
if (_glfw.linjs.inotify > 0)
|
|
||||||
addWatch(&_glfw.x11.eventLoopData, "joystick", _glfw.linjs.inotify, POLLIN, 1, NULL, NULL);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
_glfwPollMonitorsX11();
|
_glfwPollMonitorsX11();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -716,9 +731,6 @@ void _glfwPlatformTerminate(void)
|
|||||||
_glfwTerminateEGL();
|
_glfwTerminateEGL();
|
||||||
_glfwTerminateGLX();
|
_glfwTerminateGLX();
|
||||||
|
|
||||||
#if defined(__linux__)
|
|
||||||
_glfwTerminateJoysticksLinux();
|
|
||||||
#endif
|
|
||||||
finalizePollData(&_glfw.x11.eventLoopData);
|
finalizePollData(&_glfw.x11.eventLoopData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
2
glfw/x11_monitor.c
vendored
2
glfw/x11_monitor.c
vendored
@@ -169,7 +169,7 @@ void _glfwPollMonitorsX11(void)
|
|||||||
if (widthMM <= 0 || heightMM <= 0)
|
if (widthMM <= 0 || heightMM <= 0)
|
||||||
{
|
{
|
||||||
// HACK: If RandR does not provide a physical size, assume the
|
// HACK: If RandR does not provide a physical size, assume the
|
||||||
// X11 default 96 DPI and calcuate from the CRTC viewport
|
// X11 default 96 DPI and calculate from the CRTC viewport
|
||||||
// NOTE: These members are affected by rotation, unlike the mode
|
// NOTE: These members are affected by rotation, unlike the mode
|
||||||
// info and output info members
|
// info and output info members
|
||||||
widthMM = (int) (ci->width * 25.4f / 96.f);
|
widthMM = (int) (ci->width * 25.4f / 96.f);
|
||||||
|
|||||||
31
glfw/x11_platform.h
vendored
31
glfw/x11_platform.h
vendored
@@ -51,6 +51,9 @@
|
|||||||
// The XInput extension provides raw mouse motion input
|
// The XInput extension provides raw mouse motion input
|
||||||
#include <X11/extensions/XInput2.h>
|
#include <X11/extensions/XInput2.h>
|
||||||
|
|
||||||
|
// The Shape extension provides custom window shapes
|
||||||
|
#include <X11/extensions/shape.h>
|
||||||
|
|
||||||
// The libxkb library is used for improved keyboard support
|
// The libxkb library is used for improved keyboard support
|
||||||
#include "xkb_glfw.h"
|
#include "xkb_glfw.h"
|
||||||
#include "backend_utils.h"
|
#include "backend_utils.h"
|
||||||
@@ -125,6 +128,16 @@ typedef XRenderPictFormat* (* PFN_XRenderFindVisualFormat)(Display*,Visual const
|
|||||||
#define XRenderQueryVersion _glfw.x11.xrender.QueryVersion
|
#define XRenderQueryVersion _glfw.x11.xrender.QueryVersion
|
||||||
#define XRenderFindVisualFormat _glfw.x11.xrender.FindVisualFormat
|
#define XRenderFindVisualFormat _glfw.x11.xrender.FindVisualFormat
|
||||||
|
|
||||||
|
typedef Bool (* PFN_XShapeQueryExtension)(Display*,int*,int*);
|
||||||
|
typedef Status (* PFN_XShapeQueryVersion)(Display*dpy,int*,int*);
|
||||||
|
typedef void (* PFN_XShapeCombineRegion)(Display*,Window,int,int,int,Region,int);
|
||||||
|
typedef void (* PFN_XShapeCombineMask)(Display*,Window,int,int,int,Pixmap,int);
|
||||||
|
|
||||||
|
#define XShapeQueryExtension _glfw.x11.xshape.QueryExtension
|
||||||
|
#define XShapeQueryVersion _glfw.x11.xshape.QueryVersion
|
||||||
|
#define XShapeCombineRegion _glfw.x11.xshape.ShapeCombineRegion
|
||||||
|
#define XShapeCombineMask _glfw.x11.xshape.ShapeCombineMask
|
||||||
|
|
||||||
typedef VkFlags VkXlibSurfaceCreateFlagsKHR;
|
typedef VkFlags VkXlibSurfaceCreateFlagsKHR;
|
||||||
typedef VkFlags VkXcbSurfaceCreateFlagsKHR;
|
typedef VkFlags VkXcbSurfaceCreateFlagsKHR;
|
||||||
|
|
||||||
@@ -153,8 +166,6 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR)(Vk
|
|||||||
|
|
||||||
#include "posix_thread.h"
|
#include "posix_thread.h"
|
||||||
#include "glx_context.h"
|
#include "glx_context.h"
|
||||||
#include "egl_context.h"
|
|
||||||
#include "osmesa_context.h"
|
|
||||||
#if defined(__linux__)
|
#if defined(__linux__)
|
||||||
#include "linux_joystick.h"
|
#include "linux_joystick.h"
|
||||||
#else
|
#else
|
||||||
@@ -165,9 +176,6 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR)(Vk
|
|||||||
#define _glfw_dlclose(handle) dlclose(handle)
|
#define _glfw_dlclose(handle) dlclose(handle)
|
||||||
#define _glfw_dlsym(handle, name) dlsym(handle, name)
|
#define _glfw_dlsym(handle, name) dlsym(handle, name)
|
||||||
|
|
||||||
#define _GLFW_EGL_NATIVE_WINDOW ((EGLNativeWindowType) window->x11.handle)
|
|
||||||
#define _GLFW_EGL_NATIVE_DISPLAY ((EGLNativeDisplayType) _glfw.x11.display)
|
|
||||||
|
|
||||||
#define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowX11 x11
|
#define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowX11 x11
|
||||||
#define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryX11 x11
|
#define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryX11 x11
|
||||||
#define _GLFW_PLATFORM_MONITOR_STATE _GLFWmonitorX11 x11
|
#define _GLFW_PLATFORM_MONITOR_STATE _GLFWmonitorX11 x11
|
||||||
@@ -382,6 +390,19 @@ typedef struct _GLFWlibraryX11
|
|||||||
PFN_XRenderFindVisualFormat FindVisualFormat;
|
PFN_XRenderFindVisualFormat FindVisualFormat;
|
||||||
} xrender;
|
} xrender;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
bool available;
|
||||||
|
void* handle;
|
||||||
|
int major;
|
||||||
|
int minor;
|
||||||
|
int eventBase;
|
||||||
|
int errorBase;
|
||||||
|
PFN_XShapeQueryExtension QueryExtension;
|
||||||
|
PFN_XShapeCombineRegion ShapeCombineRegion;
|
||||||
|
PFN_XShapeQueryVersion QueryVersion;
|
||||||
|
PFN_XShapeCombineMask ShapeCombineMask;
|
||||||
|
} xshape;
|
||||||
|
|
||||||
EventLoopData eventLoopData;
|
EventLoopData eventLoopData;
|
||||||
|
|
||||||
} _GLFWlibraryX11;
|
} _GLFWlibraryX11;
|
||||||
|
|||||||
128
glfw/x11_window.c
vendored
128
glfw/x11_window.c
vendored
@@ -422,14 +422,16 @@ static char* convertLatin1toUTF8(const char* source)
|
|||||||
size_t size = 1;
|
size_t size = 1;
|
||||||
const char* sp;
|
const char* sp;
|
||||||
|
|
||||||
for (sp = source; *sp; sp++)
|
if (source) {
|
||||||
size += (*sp & 0x80) ? 2 : 1;
|
for (sp = source; *sp; sp++) size += (*sp & 0x80) ? 2 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
char* target = calloc(size, 1);
|
char* target = calloc(size, 1);
|
||||||
char* tp = target;
|
char* tp = target;
|
||||||
|
|
||||||
for (sp = source; *sp; sp++)
|
if (source) {
|
||||||
tp += encodeUTF8(tp, *sp);
|
for (sp = source; *sp; sp++) tp += encodeUTF8(tp, *sp);
|
||||||
|
}
|
||||||
|
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
@@ -771,7 +773,7 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
|
|||||||
|
|
||||||
if (j < formatCount)
|
if (j < formatCount)
|
||||||
{
|
{
|
||||||
XChangeProperty(_glfw.x11.display,
|
if (selectionString) XChangeProperty(_glfw.x11.display,
|
||||||
request->requestor,
|
request->requestor,
|
||||||
targets[i + 1],
|
targets[i + 1],
|
||||||
targets[i],
|
targets[i],
|
||||||
@@ -823,7 +825,7 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
|
|||||||
{
|
{
|
||||||
// The requested target is one we support
|
// The requested target is one we support
|
||||||
|
|
||||||
XChangeProperty(_glfw.x11.display,
|
if (selectionString) XChangeProperty(_glfw.x11.display,
|
||||||
request->requestor,
|
request->requestor,
|
||||||
request->property,
|
request->property,
|
||||||
request->target,
|
request->target,
|
||||||
@@ -954,8 +956,10 @@ static const char* getSelectionString(Atom selection)
|
|||||||
(XPointer) ¬ification))
|
(XPointer) ¬ification))
|
||||||
{
|
{
|
||||||
monotonic_t time = glfwGetTime();
|
monotonic_t time = glfwGetTime();
|
||||||
if (time - start > s_to_monotonic_t(2ll))
|
if (time - start > s_to_monotonic_t(2ll)) {
|
||||||
|
free(string);
|
||||||
return "";
|
return "";
|
||||||
|
}
|
||||||
waitForX11Event(s_to_monotonic_t(2ll) - (time - start));
|
waitForX11Event(s_to_monotonic_t(2ll) - (time - start));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1176,15 +1180,26 @@ static void processEvent(XEvent *event)
|
|||||||
else if (event->type == _glfw.x11.xkb.eventBase)
|
else if (event->type == _glfw.x11.xkb.eventBase)
|
||||||
{
|
{
|
||||||
XkbEvent *kb_event = (XkbEvent*)event;
|
XkbEvent *kb_event = (XkbEvent*)event;
|
||||||
|
if (kb_event->any.device != (unsigned int)_glfw.x11.xkb.keyboard_device_id) return;
|
||||||
switch(kb_event->any.xkb_type) {
|
switch(kb_event->any.xkb_type) {
|
||||||
case XkbNewKeyboardNotify: {
|
case XkbNewKeyboardNotify: {
|
||||||
int32_t old_id = _glfw.x11.xkb.keyboard_device_id;
|
XkbNewKeyboardNotifyEvent *newkb_event = (XkbNewKeyboardNotifyEvent*)kb_event;
|
||||||
if (!glfw_xkb_update_x11_keyboard_id(&_glfw.x11.xkb)) return;
|
if (_glfw.hints.init.debugKeyboard) printf(
|
||||||
if (old_id != _glfw.x11.xkb.keyboard_device_id) keymap_dirty = true;
|
"Got XkbNewKeyboardNotify event with changes: key codes: %d geometry: %d device id: %d\n",
|
||||||
|
!!(newkb_event->changed & XkbNKN_KeycodesMask), !!(newkb_event->changed & XkbNKN_GeometryMask),
|
||||||
|
!!(newkb_event->changed & XkbNKN_DeviceIDMask));
|
||||||
|
if (newkb_event->changed & XkbNKN_DeviceIDMask) {
|
||||||
|
keymap_dirty = true;
|
||||||
|
if (!glfw_xkb_update_x11_keyboard_id(&_glfw.x11.xkb)) return;
|
||||||
|
}
|
||||||
|
if (newkb_event->changed & XkbNKN_KeycodesMask) {
|
||||||
|
keymap_dirty = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
/* fallthrough */
|
|
||||||
case XkbMapNotify:
|
case XkbMapNotify:
|
||||||
{
|
{
|
||||||
|
if (_glfw.hints.init.debugKeyboard) printf("Got XkbMapNotify event, keymaps will be reloaded\n");
|
||||||
keymap_dirty = true;
|
keymap_dirty = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1507,7 +1522,7 @@ static void processEvent(XEvent *event)
|
|||||||
count = 3;
|
count = 3;
|
||||||
formats = (Atom*) event->xclient.data.l + 2;
|
formats = (Atom*) event->xclient.data.l + 2;
|
||||||
}
|
}
|
||||||
char **atom_names = calloc(count, sizeof(char**));
|
char **atom_names = calloc(count, sizeof(char*));
|
||||||
if (atom_names) {
|
if (atom_names) {
|
||||||
get_atom_names(formats, count, atom_names);
|
get_atom_names(formats, count, atom_names);
|
||||||
|
|
||||||
@@ -2449,13 +2464,19 @@ int _glfwPlatformWindowHovered(_GLFWwindow* window)
|
|||||||
int rootX, rootY, childX, childY;
|
int rootX, rootY, childX, childY;
|
||||||
unsigned int mask;
|
unsigned int mask;
|
||||||
|
|
||||||
if (!XQueryPointer(_glfw.x11.display, w,
|
_glfwGrabErrorHandlerX11();
|
||||||
&root, &w, &rootX, &rootY, &childX, &childY, &mask))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (w == window->x11.handle)
|
const Bool result = XQueryPointer(_glfw.x11.display, w,
|
||||||
|
&root, &w, &rootX, &rootY,
|
||||||
|
&childX, &childY, &mask);
|
||||||
|
|
||||||
|
_glfwReleaseErrorHandlerX11();
|
||||||
|
|
||||||
|
if (_glfw.x11.errorCode == BadWindow)
|
||||||
|
w = _glfw.x11.root;
|
||||||
|
else if (!result)
|
||||||
|
return false;
|
||||||
|
else if (w == window->x11.handle)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2569,6 +2590,25 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled)
|
|||||||
XFlush(_glfw.x11.display);
|
XFlush(_glfw.x11.display);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled)
|
||||||
|
{
|
||||||
|
if (!_glfw.x11.xshape.available)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (enabled)
|
||||||
|
{
|
||||||
|
Region region = XCreateRegion();
|
||||||
|
XShapeCombineRegion(_glfw.x11.display, window->x11.handle,
|
||||||
|
ShapeInput, 0, 0, region, ShapeSet);
|
||||||
|
XDestroyRegion(region);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
XShapeCombineMask(_glfw.x11.display, window->x11.handle,
|
||||||
|
ShapeInput, 0, 0, None, ShapeSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window)
|
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window)
|
||||||
{
|
{
|
||||||
float opacity = 1.f;
|
float opacity = 1.f;
|
||||||
@@ -2617,7 +2657,8 @@ _glfwDispatchX11Events(void) {
|
|||||||
unsigned dispatched = 0;
|
unsigned dispatched = 0;
|
||||||
|
|
||||||
#if defined(__linux__)
|
#if defined(__linux__)
|
||||||
if (_glfw.hints.init.enableJoysticks) _glfwDetectJoystickConnectionLinux();
|
if (_glfw.joysticksInitialized)
|
||||||
|
_glfwDetectJoystickConnectionLinux();
|
||||||
#endif
|
#endif
|
||||||
dispatched += dispatch_x11_queued_events(XEventsQueued(_glfw.x11.display, QueuedAfterFlush));
|
dispatched += dispatch_x11_queued_events(XEventsQueued(_glfw.x11.display, QueuedAfterFlush));
|
||||||
|
|
||||||
@@ -2844,6 +2885,55 @@ const char* _glfwPlatformGetPrimarySelectionString(void)
|
|||||||
return getSelectionString(_glfw.x11.PRIMARY);
|
return getSelectionString(_glfw.x11.PRIMARY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs)
|
||||||
|
{
|
||||||
|
if (_glfw.egl.ANGLE_platform_angle)
|
||||||
|
{
|
||||||
|
int type = 0;
|
||||||
|
|
||||||
|
if (_glfw.egl.ANGLE_platform_angle_opengl)
|
||||||
|
{
|
||||||
|
if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_OPENGL)
|
||||||
|
type = EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_glfw.egl.ANGLE_platform_angle_vulkan)
|
||||||
|
{
|
||||||
|
if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_VULKAN)
|
||||||
|
type = EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type)
|
||||||
|
{
|
||||||
|
*attribs = calloc(5, sizeof(EGLint));
|
||||||
|
(*attribs)[0] = EGL_PLATFORM_ANGLE_TYPE_ANGLE;
|
||||||
|
(*attribs)[1] = type;
|
||||||
|
(*attribs)[2] = EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE;
|
||||||
|
(*attribs)[3] = EGL_PLATFORM_X11_EXT;
|
||||||
|
(*attribs)[4] = EGL_NONE;
|
||||||
|
return EGL_PLATFORM_ANGLE_ANGLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_glfw.egl.EXT_platform_base && _glfw.egl.EXT_platform_x11)
|
||||||
|
return EGL_PLATFORM_X11_EXT;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void)
|
||||||
|
{
|
||||||
|
return _glfw.x11.display;
|
||||||
|
}
|
||||||
|
|
||||||
|
EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window)
|
||||||
|
{
|
||||||
|
if (_glfw.egl.platform)
|
||||||
|
return &window->x11.handle;
|
||||||
|
else
|
||||||
|
return (EGLNativeWindowType) window->x11.handle;
|
||||||
|
}
|
||||||
|
|
||||||
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
|
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
|
||||||
{
|
{
|
||||||
if (!_glfw.vk.KHR_surface)
|
if (!_glfw.vk.KHR_surface)
|
||||||
|
|||||||
56
glfw/xkb_glfw.c
vendored
56
glfw/xkb_glfw.c
vendored
@@ -371,9 +371,31 @@ load_compose_tables(_GLFWXKBData *xkb) {
|
|||||||
xkb_compose_table_unref(compose_table);
|
xkb_compose_table_unref(compose_table);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline xkb_mod_mask_t
|
||||||
|
active_unknown_modifiers(_GLFWXKBData *xkb, struct xkb_state *state) {
|
||||||
|
size_t i = 0;
|
||||||
|
xkb_mod_mask_t ans = 0;
|
||||||
|
while (xkb->unknownModifiers[i] != XKB_MOD_INVALID) {
|
||||||
|
if (xkb_state_mod_index_is_active(state, xkb->unknownModifiers[i], XKB_STATE_MODS_EFFECTIVE)) ans |= (1 << xkb->unknownModifiers[i]);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
update_modifiers(_GLFWXKBData *xkb) {
|
||||||
|
XKBStateGroup *group = &xkb->states;
|
||||||
|
#define S(attr, name) if (xkb_state_mod_index_is_active(group->state, xkb->attr##Idx, XKB_STATE_MODS_EFFECTIVE)) group->modifiers |= GLFW_MOD_##name
|
||||||
|
S(control, CONTROL); S(alt, ALT); S(shift, SHIFT); S(super, SUPER); S(capsLock, CAPS_LOCK); S(numLock, NUM_LOCK);
|
||||||
|
#undef S
|
||||||
|
xkb->states.activeUnknownModifiers = active_unknown_modifiers(xkb, xkb->states.state);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
glfw_xkb_compile_keymap(_GLFWXKBData *xkb, const char *map_str) {
|
glfw_xkb_compile_keymap(_GLFWXKBData *xkb, const char *map_str) {
|
||||||
const char *err;
|
const char *err;
|
||||||
|
debug("Loading new XKB keymaps\n");
|
||||||
release_keyboard_data(xkb);
|
release_keyboard_data(xkb);
|
||||||
err = load_keymaps(xkb, map_str);
|
err = load_keymaps(xkb, map_str);
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -396,36 +418,17 @@ glfw_xkb_compile_keymap(_GLFWXKBData *xkb, const char *map_str) {
|
|||||||
S(capsLock, XKB_MOD_NAME_CAPS);
|
S(capsLock, XKB_MOD_NAME_CAPS);
|
||||||
S(numLock, XKB_MOD_NAME_NUM);
|
S(numLock, XKB_MOD_NAME_NUM);
|
||||||
#undef S
|
#undef S
|
||||||
size_t capacity = sizeof(xkb->unknownModifiers)/sizeof(xkb->unknownModifiers[0]), j = 0;
|
size_t capacity = arraysz(xkb->unknownModifiers), j = 0;
|
||||||
for (xkb_mod_index_t i = 0; i < capacity; i++) xkb->unknownModifiers[i] = XKB_MOD_INVALID;
|
for (xkb_mod_index_t i = 0; i < capacity; i++) xkb->unknownModifiers[i] = XKB_MOD_INVALID;
|
||||||
for (xkb_mod_index_t i = 0; i < xkb_keymap_num_mods(xkb->keymap) && j < capacity - 1; i++) {
|
for (xkb_mod_index_t i = 0; i < xkb_keymap_num_mods(xkb->keymap) && j < capacity - 1; i++) {
|
||||||
if (i != xkb->controlIdx && i != xkb->altIdx && i != xkb->shiftIdx && i != xkb->superIdx && i != xkb->capsLockIdx && i != xkb->numLockIdx) xkb->unknownModifiers[j++] = i;
|
if (i != xkb->controlIdx && i != xkb->altIdx && i != xkb->shiftIdx && i != xkb->superIdx && i != xkb->capsLockIdx && i != xkb->numLockIdx) xkb->unknownModifiers[j++] = i;
|
||||||
}
|
}
|
||||||
xkb->states.modifiers = 0;
|
xkb->states.modifiers = 0;
|
||||||
xkb->states.activeUnknownModifiers = 0;
|
xkb->states.activeUnknownModifiers = 0;
|
||||||
|
update_modifiers(xkb);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline xkb_mod_mask_t
|
|
||||||
active_unknown_modifiers(_GLFWXKBData *xkb, struct xkb_state *state) {
|
|
||||||
size_t i = 0;
|
|
||||||
xkb_mod_mask_t ans = 0;
|
|
||||||
while (xkb->unknownModifiers[i] != XKB_MOD_INVALID) {
|
|
||||||
if (xkb_state_mod_index_is_active(state, xkb->unknownModifiers[i], XKB_STATE_MODS_EFFECTIVE)) ans |= (1 << xkb->unknownModifiers[i]);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
return ans;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
update_modifiers(_GLFWXKBData *xkb, XKBStateGroup *group) {
|
|
||||||
#define S(attr, name) if (xkb_state_mod_index_is_active(group->state, xkb->attr##Idx, XKB_STATE_MODS_EFFECTIVE)) group->modifiers |= GLFW_MOD_##name
|
|
||||||
S(control, CONTROL); S(alt, ALT); S(shift, SHIFT); S(super, SUPER); S(capsLock, CAPS_LOCK); S(numLock, NUM_LOCK);
|
|
||||||
#undef S
|
|
||||||
xkb->states.activeUnknownModifiers = active_unknown_modifiers(xkb, xkb->states.state);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
glfw_xkb_update_modifiers(_GLFWXKBData *xkb, xkb_mod_mask_t depressed, xkb_mod_mask_t latched, xkb_mod_mask_t locked, xkb_layout_index_t base_group, xkb_layout_index_t latched_group, xkb_layout_index_t locked_group) {
|
glfw_xkb_update_modifiers(_GLFWXKBData *xkb, xkb_mod_mask_t depressed, xkb_mod_mask_t latched, xkb_mod_mask_t locked, xkb_layout_index_t base_group, xkb_layout_index_t latched_group, xkb_layout_index_t locked_group) {
|
||||||
if (!xkb->keymap) return;
|
if (!xkb->keymap) return;
|
||||||
@@ -434,7 +437,7 @@ glfw_xkb_update_modifiers(_GLFWXKBData *xkb, xkb_mod_mask_t depressed, xkb_mod_m
|
|||||||
// We have to update the groups in clean_state, as they change for
|
// We have to update the groups in clean_state, as they change for
|
||||||
// different keyboard layouts, see https://github.com/kovidgoyal/kitty/issues/488
|
// different keyboard layouts, see https://github.com/kovidgoyal/kitty/issues/488
|
||||||
xkb_state_update_mask(xkb->states.clean_state, 0, 0, 0, base_group, latched_group, locked_group);
|
xkb_state_update_mask(xkb->states.clean_state, 0, 0, 0, base_group, latched_group, locked_group);
|
||||||
update_modifiers(xkb, &xkb->states);
|
update_modifiers(xkb);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@@ -503,14 +506,19 @@ static inline const char*
|
|||||||
format_xkb_mods(_GLFWXKBData *xkb, const char* name, xkb_mod_mask_t mods) {
|
format_xkb_mods(_GLFWXKBData *xkb, const char* name, xkb_mod_mask_t mods) {
|
||||||
static char buf[512];
|
static char buf[512];
|
||||||
char *p = buf, *s;
|
char *p = buf, *s;
|
||||||
#define pr(x) p += snprintf(p, sizeof(buf) - (p - buf) - 1, "%s", x)
|
#define pr(x) { \
|
||||||
|
int num_needed = -1; \
|
||||||
|
ssize_t space_left = sizeof(buf) - (p - buf) - 1; \
|
||||||
|
if (space_left > 0) num_needed = snprintf(p, space_left, "%s", x); \
|
||||||
|
if (num_needed > 0) p += num_needed; \
|
||||||
|
}
|
||||||
pr(name); pr(": ");
|
pr(name); pr(": ");
|
||||||
s = p;
|
s = p;
|
||||||
for (xkb_mod_index_t i = 0; i < xkb_keymap_num_mods(xkb->keymap); i++) {
|
for (xkb_mod_index_t i = 0; i < xkb_keymap_num_mods(xkb->keymap); i++) {
|
||||||
xkb_mod_mask_t m = 1 << i;
|
xkb_mod_mask_t m = 1 << i;
|
||||||
if (m & mods) { pr(xkb_keymap_mod_get_name(xkb->keymap, i)); pr("+"); }
|
if (m & mods) { pr(xkb_keymap_mod_get_name(xkb->keymap, i)); pr("+"); }
|
||||||
}
|
}
|
||||||
if (p == s) pr("none");
|
if (p == s) { pr("none"); }
|
||||||
else p--;
|
else p--;
|
||||||
pr(" ");
|
pr(" ");
|
||||||
#undef pr
|
#undef pr
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ from kitty.constants import cache_dir
|
|||||||
from kitty.typing import BossType
|
from kitty.typing import BossType
|
||||||
|
|
||||||
from ..tui.handler import result_handler
|
from ..tui.handler import result_handler
|
||||||
from ..tui.operations import alternate_screen, set_cursor_visible, styled
|
from ..tui.operations import alternate_screen, styled
|
||||||
|
from ..tui.utils import get_key_press
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import readline
|
import readline
|
||||||
@@ -70,7 +71,7 @@ class HistoryCompleter:
|
|||||||
def option_text() -> str:
|
def option_text() -> str:
|
||||||
return '''\
|
return '''\
|
||||||
--type -t
|
--type -t
|
||||||
choices=line,yesno
|
choices=line,yesno,choices
|
||||||
default=line
|
default=line
|
||||||
Type of input. Defaults to asking for a line of text.
|
Type of input. Defaults to asking for a line of text.
|
||||||
|
|
||||||
@@ -83,6 +84,16 @@ message is shown.
|
|||||||
--name -n
|
--name -n
|
||||||
The name for this question. Used to store history of previous answers which can
|
The name for this question. Used to store history of previous answers which can
|
||||||
be used for completions and via the browse history readline bindings.
|
be used for completions and via the browse history readline bindings.
|
||||||
|
|
||||||
|
|
||||||
|
--choice -c
|
||||||
|
type=list
|
||||||
|
dest=choices
|
||||||
|
A choice for the choices type. Every choice has the syntax: letter:text Where
|
||||||
|
letter is the accelerator key and text is the corresponding text. There can be
|
||||||
|
an optional color specification after the letter to indicate what color it should
|
||||||
|
be.
|
||||||
|
For example: y:Yes and n;red:No
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
@@ -97,23 +108,34 @@ class Response(TypedDict):
|
|||||||
response: Optional[str]
|
response: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
def yesno(cli_opts: AskCLIOptions, items: List[str]) -> Response:
|
def choice(cli_opts: AskCLIOptions, items: List[str]) -> Response:
|
||||||
import tty
|
|
||||||
with alternate_screen():
|
with alternate_screen():
|
||||||
if cli_opts.message:
|
if cli_opts.message:
|
||||||
print(styled(cli_opts.message, bold=True))
|
print(styled(cli_opts.message, bold=True))
|
||||||
print()
|
print()
|
||||||
print(' ', styled('Y', fg='green') + 'es', ' ', styled('N', fg='red') + 'o', set_cursor_visible(False))
|
allowed = ''
|
||||||
sys.stdout.flush()
|
for choice in cli_opts.choices:
|
||||||
tty.setraw(sys.stdin.fileno())
|
color = 'green'
|
||||||
try:
|
letter, text = choice.split(':', maxsplit=1)
|
||||||
response = sys.stdin.buffer.read(1)
|
if ';' in letter:
|
||||||
yes = response in (b'y', b'Y', b'\r', b'\n' b' ')
|
letter, color = letter.split(';', maxsplit=1)
|
||||||
return {'items': items, 'response': 'y' if yes else 'n'}
|
letter = letter.lower()
|
||||||
finally:
|
idx = text.lower().index(letter)
|
||||||
sys.stdout.write(set_cursor_visible(True))
|
allowed += letter
|
||||||
tty.setcbreak(sys.stdin.fileno())
|
print(text[:idx], styled(text[idx], fg=color), text[idx + 1:], sep='', end=' ')
|
||||||
sys.stdout.flush()
|
print()
|
||||||
|
response = get_key_press(allowed, '')
|
||||||
|
return {'items': items, 'response': response}
|
||||||
|
|
||||||
|
|
||||||
|
def yesno(cli_opts: AskCLIOptions, items: List[str]) -> Response:
|
||||||
|
with alternate_screen():
|
||||||
|
if cli_opts.message:
|
||||||
|
print(styled(cli_opts.message, bold=True))
|
||||||
|
print()
|
||||||
|
print(' ', styled('Y', fg='green') + 'es', ' ', styled('N', fg='red') + 'o')
|
||||||
|
response = get_key_press('yn', 'n')
|
||||||
|
return {'items': items, 'response': response}
|
||||||
|
|
||||||
|
|
||||||
def main(args: List[str]) -> Response:
|
def main(args: List[str]) -> Response:
|
||||||
@@ -132,6 +154,8 @@ def main(args: List[str]) -> Response:
|
|||||||
|
|
||||||
if cli_opts.type == 'yesno':
|
if cli_opts.type == 'yesno':
|
||||||
return yesno(cli_opts, items)
|
return yesno(cli_opts, items)
|
||||||
|
if cli_opts.type == 'choices':
|
||||||
|
return choice(cli_opts, items)
|
||||||
|
|
||||||
import readline as rl
|
import readline as rl
|
||||||
readline = rl
|
readline = rl
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ end:
|
|||||||
if (job_data[i] && job_data[i]->started) wait_for_thread(threads, i);
|
if (job_data[i] && job_data[i]->started) wait_for_thread(threads, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (i = 0; i < num_threads; i++) job_data[i] = free_job(job_data[i]);
|
if (job_data) { for (i = 0; i < num_threads; i++) job_data[i] = free_job(job_data[i]); }
|
||||||
free(job_data);
|
free(job_data);
|
||||||
free_threads(threads);
|
free_threads(threads);
|
||||||
return ret;
|
return ret;
|
||||||
@@ -130,7 +130,7 @@ run_search(Options *opts, GlobalData *global, const char * const *lines, const s
|
|||||||
ALLOC_VEC(text_t, chars, 8192 * 20);
|
ALLOC_VEC(text_t, chars, 8192 * 20);
|
||||||
if (chars.data == NULL) return 1;
|
if (chars.data == NULL) return 1;
|
||||||
ALLOC_VEC(Candidate, candidates, 8192);
|
ALLOC_VEC(Candidate, candidates, 8192);
|
||||||
if (candidates.data == NULL) { FREE_VEC(chars) return 1; }
|
if (candidates.data == NULL) { FREE_VEC(chars); return 1; }
|
||||||
|
|
||||||
for (size_t i = 0; i < num_lines; i++) {
|
for (size_t i = 0; i < num_lines; i++) {
|
||||||
sz = sizes[i];
|
sz = sizes[i];
|
||||||
|
|||||||
@@ -28,8 +28,9 @@
|
|||||||
#define ENSURE_SPACE(TYPE, vec, amt) \
|
#define ENSURE_SPACE(TYPE, vec, amt) \
|
||||||
if (vec.size + amt >= vec.capacity) { \
|
if (vec.size + amt >= vec.capacity) { \
|
||||||
vec.capacity = MAX(vec.capacity * 2, vec.size + amt); \
|
vec.capacity = MAX(vec.capacity * 2, vec.size + amt); \
|
||||||
vec.data = (TYPE*)realloc(vec.data, sizeof(TYPE) * vec.capacity); \
|
void *temp = realloc(vec.data, sizeof(TYPE) * vec.capacity); \
|
||||||
if (vec.data == NULL) { REPORT_OOM; ret = 1; break; } \
|
if (temp == NULL) { REPORT_OOM; ret = 1; free(vec.data); vec.data = NULL; vec.size = 0; vec.capacity = 0; break; } \
|
||||||
|
else vec.data = temp; \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define NEXT(vec) (vec.data[vec.size])
|
#define NEXT(vec) (vec.data[vec.size])
|
||||||
|
|||||||
@@ -45,12 +45,21 @@ ESCAPE = K['ESCAPE']
|
|||||||
|
|
||||||
class Mark:
|
class Mark:
|
||||||
|
|
||||||
__slots__ = ('index', 'start', 'end', 'text', 'groupdict')
|
__slots__ = ('index', 'start', 'end', 'text', 'is_hyperlink', 'group_id', 'groupdict')
|
||||||
|
|
||||||
def __init__(self, index: int, start: int, end: int, text: str, groupdict: Any):
|
def __init__(
|
||||||
|
self,
|
||||||
|
index: int, start: int, end: int,
|
||||||
|
text: str,
|
||||||
|
groupdict: Any,
|
||||||
|
is_hyperlink: bool = False,
|
||||||
|
group_id: Optional[str] = None
|
||||||
|
):
|
||||||
self.index, self.start, self.end = index, start, end
|
self.index, self.start, self.end = index, start, end
|
||||||
self.text = text
|
self.text = text
|
||||||
self.groupdict = groupdict
|
self.groupdict = groupdict
|
||||||
|
self.is_hyperlink = is_hyperlink
|
||||||
|
self.group_id = group_id
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=2048)
|
@lru_cache(maxsize=2048)
|
||||||
@@ -72,7 +81,7 @@ def decode_hint(x: str, alphabet: str = DEFAULT_HINT_ALPHABET) -> int:
|
|||||||
return i
|
return i
|
||||||
|
|
||||||
|
|
||||||
def highlight_mark(m: Mark, text: str, current_input: str, alphabet: str) -> str:
|
def highlight_mark(m: Mark, text: str, current_input: str, alphabet: str, colors: Dict[str, str]) -> str:
|
||||||
hint = encode_hint(m.index, alphabet)
|
hint = encode_hint(m.index, alphabet)
|
||||||
if current_input and not hint.startswith(current_input):
|
if current_input and not hint.startswith(current_input):
|
||||||
return faint(text)
|
return faint(text)
|
||||||
@@ -80,19 +89,19 @@ def highlight_mark(m: Mark, text: str, current_input: str, alphabet: str) -> str
|
|||||||
text = text[len(hint):]
|
text = text[len(hint):]
|
||||||
return styled(
|
return styled(
|
||||||
hint,
|
hint,
|
||||||
fg='black',
|
fg=colors['foreground'],
|
||||||
bg='green',
|
bg=colors['background'],
|
||||||
bold=True
|
bold=True
|
||||||
) + styled(
|
) + styled(
|
||||||
text, fg='gray', fg_intense=True, bold=True
|
text, fg=colors['text'], fg_intense=True, bold=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def render(text: str, current_input: str, all_marks: Sequence[Mark], ignore_mark_indices: Set[int], alphabet: str) -> str:
|
def render(text: str, current_input: str, all_marks: Sequence[Mark], ignore_mark_indices: Set[int], alphabet: str, colors: Dict[str, str]) -> str:
|
||||||
for mark in reversed(all_marks):
|
for mark in reversed(all_marks):
|
||||||
if mark.index in ignore_mark_indices:
|
if mark.index in ignore_mark_indices:
|
||||||
continue
|
continue
|
||||||
mtext = highlight_mark(mark, text[mark.start:mark.end], current_input, alphabet)
|
mtext = highlight_mark(mark, text[mark.start:mark.end], current_input, alphabet, colors)
|
||||||
text = text[:mark.start] + mtext + text[mark.end:]
|
text = text[:mark.start] + mtext + text[mark.end:]
|
||||||
|
|
||||||
text = text.replace('\0', '')
|
text = text.replace('\0', '')
|
||||||
@@ -105,6 +114,9 @@ class Hints(Handler):
|
|||||||
def __init__(self, text: str, all_marks: Sequence[Mark], index_map: Dict[int, Mark], args: HintsCLIOptions):
|
def __init__(self, text: str, all_marks: Sequence[Mark], index_map: Dict[int, Mark], args: HintsCLIOptions):
|
||||||
self.text, self.index_map = text, index_map
|
self.text, self.index_map = text, index_map
|
||||||
self.alphabet = args.alphabet or DEFAULT_HINT_ALPHABET
|
self.alphabet = args.alphabet or DEFAULT_HINT_ALPHABET
|
||||||
|
self.colors = {'foreground': args.hints_foreground_color,
|
||||||
|
'background': args.hints_background_color,
|
||||||
|
'text': args.hints_text_color}
|
||||||
self.all_marks = all_marks
|
self.all_marks = all_marks
|
||||||
self.ignore_mark_indices: Set[int] = set()
|
self.ignore_mark_indices: Set[int] = set()
|
||||||
self.args = args
|
self.args = args
|
||||||
@@ -198,7 +210,7 @@ class Hints(Handler):
|
|||||||
|
|
||||||
def draw_screen(self) -> None:
|
def draw_screen(self) -> None:
|
||||||
if self.current_text is None:
|
if self.current_text is None:
|
||||||
self.current_text = render(self.text, self.current_input, self.all_marks, self.ignore_mark_indices, self.alphabet)
|
self.current_text = render(self.text, self.current_input, self.all_marks, self.ignore_mark_indices, self.alphabet, self.colors)
|
||||||
self.cmd.clear_screen()
|
self.cmd.clear_screen()
|
||||||
self.write(self.current_text)
|
self.write(self.current_text)
|
||||||
|
|
||||||
@@ -366,13 +378,66 @@ def load_custom_processor(customize_processing: str) -> Any:
|
|||||||
return runpy.run_path(custom_path, run_name='__main__')
|
return runpy.run_path(custom_path, run_name='__main__')
|
||||||
|
|
||||||
|
|
||||||
|
def remove_sgr(text: str) -> str:
|
||||||
|
return re.sub(r'\x1b\[.*?m', '', text)
|
||||||
|
|
||||||
|
|
||||||
|
def process_hyperlinks(text: str) -> Tuple[str, Tuple[Mark, ...]]:
|
||||||
|
hyperlinks: List[Mark] = []
|
||||||
|
removed_size = idx = 0
|
||||||
|
active_hyperlink_url: Optional[str] = None
|
||||||
|
active_hyperlink_id: Optional[str] = None
|
||||||
|
active_hyperlink_start_offset = 0
|
||||||
|
|
||||||
|
def add_hyperlink(end: int) -> None:
|
||||||
|
nonlocal idx, active_hyperlink_url, active_hyperlink_id, active_hyperlink_start_offset
|
||||||
|
assert active_hyperlink_url is not None
|
||||||
|
hyperlinks.append(Mark(
|
||||||
|
idx, active_hyperlink_start_offset, end,
|
||||||
|
active_hyperlink_url,
|
||||||
|
groupdict={},
|
||||||
|
is_hyperlink=True, group_id=active_hyperlink_id
|
||||||
|
))
|
||||||
|
active_hyperlink_url = active_hyperlink_id = None
|
||||||
|
active_hyperlink_start_offset = 0
|
||||||
|
idx += 1
|
||||||
|
|
||||||
|
def process_hyperlink(m: re.Match) -> str:
|
||||||
|
nonlocal removed_size, active_hyperlink_url, active_hyperlink_id, active_hyperlink_start_offset
|
||||||
|
raw = m.group()
|
||||||
|
start = m.start() - removed_size
|
||||||
|
removed_size += len(raw)
|
||||||
|
if active_hyperlink_url is not None:
|
||||||
|
add_hyperlink(start)
|
||||||
|
raw = raw[4:-2]
|
||||||
|
parts = raw.split(';', 1)
|
||||||
|
if len(parts) == 2 and parts[1]:
|
||||||
|
active_hyperlink_url = parts[1]
|
||||||
|
active_hyperlink_start_offset = start
|
||||||
|
if parts[0]:
|
||||||
|
for entry in parts[0].split(':'):
|
||||||
|
if entry.startswith('id=') and len(entry) > 3:
|
||||||
|
active_hyperlink_id = entry[3:]
|
||||||
|
break
|
||||||
|
|
||||||
|
return ''
|
||||||
|
|
||||||
|
text = re.sub(r'\x1b\]8.+?\x1b\\', process_hyperlink, text)
|
||||||
|
if active_hyperlink_url is not None:
|
||||||
|
add_hyperlink(len(text))
|
||||||
|
return text, tuple(hyperlinks)
|
||||||
|
|
||||||
|
|
||||||
def run(args: HintsCLIOptions, text: str, extra_cli_args: Sequence[str] = ()) -> Optional[Dict[str, Any]]:
|
def run(args: HintsCLIOptions, text: str, extra_cli_args: Sequence[str] = ()) -> Optional[Dict[str, Any]]:
|
||||||
try:
|
try:
|
||||||
text = parse_input(text)
|
text = parse_input(remove_sgr(text))
|
||||||
|
text, hyperlinks = process_hyperlinks(text)
|
||||||
pattern, post_processors = functions_for(args)
|
pattern, post_processors = functions_for(args)
|
||||||
if args.type == 'linenum':
|
if args.type == 'linenum':
|
||||||
args.customize_processing = '::linenum::'
|
args.customize_processing = '::linenum::'
|
||||||
if args.customize_processing:
|
if args.type == 'hyperlink':
|
||||||
|
all_marks = hyperlinks
|
||||||
|
elif args.customize_processing:
|
||||||
m = load_custom_processor(args.customize_processing)
|
m = load_custom_processor(args.customize_processing)
|
||||||
if 'mark' in m:
|
if 'mark' in m:
|
||||||
all_marks = tuple(m['mark'](text, args, Mark, extra_cli_args))
|
all_marks = tuple(m['mark'](text, args, Mark, extra_cli_args))
|
||||||
@@ -381,9 +446,8 @@ def run(args: HintsCLIOptions, text: str, extra_cli_args: Sequence[str] = ()) ->
|
|||||||
else:
|
else:
|
||||||
all_marks = tuple(mark(pattern, post_processors, text, args))
|
all_marks = tuple(mark(pattern, post_processors, text, args))
|
||||||
if not all_marks:
|
if not all_marks:
|
||||||
input(_('No {} found, press Enter to quit.').format(
|
none_of = {'url': 'URLs', 'hyperlink': 'hyperlinks'}.get(args.type, 'matches')
|
||||||
'URLs' if args.type == 'url' else 'matches'
|
input(_('No {} found, press Enter to quit.').format(none_of))
|
||||||
))
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
largest_index = all_marks[-1].index
|
largest_index = all_marks[-1].index
|
||||||
@@ -418,7 +482,7 @@ programs.
|
|||||||
|
|
||||||
--type
|
--type
|
||||||
default=url
|
default=url
|
||||||
choices=url,regex,path,line,hash,word,linenum
|
choices=url,regex,path,line,hash,word,linenum,hyperlink
|
||||||
The type of text to search for. A value of :code:`linenum` is special, it looks
|
The type of text to search for. A value of :code:`linenum` is special, it looks
|
||||||
for error messages using the pattern specified with :option:`--regex`, which
|
for error messages using the pattern specified with :option:`--regex`, which
|
||||||
must have the named groups, :code:`path` and :code:`line`. If not specified,
|
must have the named groups, :code:`path` and :code:`line`. If not specified,
|
||||||
@@ -513,6 +577,24 @@ type=bool-set
|
|||||||
Have the hints increase from top to bottom instead of decreasing from top to bottom.
|
Have the hints increase from top to bottom instead of decreasing from top to bottom.
|
||||||
|
|
||||||
|
|
||||||
|
--hints-foreground-color
|
||||||
|
default=black
|
||||||
|
type=str
|
||||||
|
The foreground color for hints
|
||||||
|
|
||||||
|
|
||||||
|
--hints-background-color
|
||||||
|
default=green
|
||||||
|
type=str
|
||||||
|
The background color for hints
|
||||||
|
|
||||||
|
|
||||||
|
--hints-text-color
|
||||||
|
default=gray
|
||||||
|
type=str
|
||||||
|
The foreground color for text pointed to by the hints
|
||||||
|
|
||||||
|
|
||||||
--customize-processing
|
--customize-processing
|
||||||
Name of a python file in the kitty config directory which will be imported to provide
|
Name of a python file in the kitty config directory which will be imported to provide
|
||||||
custom implementations for pattern finding and performing actions
|
custom implementations for pattern finding and performing actions
|
||||||
@@ -552,14 +634,19 @@ def main(args: List[str]) -> Optional[Dict[str, Any]]:
|
|||||||
if items and not (opts.customize_processing or opts.type == 'linenum'):
|
if items and not (opts.customize_processing or opts.type == 'linenum'):
|
||||||
print('Extra command line arguments present: {}'.format(' '.join(items)), file=sys.stderr)
|
print('Extra command line arguments present: {}'.format(' '.join(items)), file=sys.stderr)
|
||||||
input(_('Press Enter to quit'))
|
input(_('Press Enter to quit'))
|
||||||
return run(opts, text, items)
|
try:
|
||||||
|
return run(opts, text, items)
|
||||||
|
except Exception:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
input(_('Press Enter to quit'))
|
||||||
|
|
||||||
|
|
||||||
def linenum_handle_result(args: List[str], data: Dict[str, Any], target_window_id: int, boss: BossType, extra_cli_args: Sequence[str], *a: Any) -> None:
|
def linenum_handle_result(args: List[str], data: Dict[str, Any], target_window_id: int, boss: BossType, extra_cli_args: Sequence[str], *a: Any) -> None:
|
||||||
for m, g in zip(data['match'], data['groupdicts']):
|
for m, g in zip(data['match'], data['groupdicts']):
|
||||||
if m:
|
if m:
|
||||||
path, line = g['path'], g['line']
|
path, line = g['path'], g['line']
|
||||||
path = path.split(':')[-1]
|
path = os.path.expanduser(path.split(':')[-1])
|
||||||
line = int(line)
|
line = int(line)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
@@ -583,7 +670,7 @@ def linenum_handle_result(args: List[str], data: Dict[str, Any], target_window_i
|
|||||||
}[action])(*cmd)
|
}[action])(*cmd)
|
||||||
|
|
||||||
|
|
||||||
@result_handler(type_of_input='screen')
|
@result_handler(type_of_input='screen-ansi')
|
||||||
def handle_result(args: List[str], data: Dict[str, Any], target_window_id: int, boss: BossType) -> None:
|
def handle_result(args: List[str], data: Dict[str, Any], target_window_id: int, boss: BossType) -> None:
|
||||||
if data['customize_processing']:
|
if data['customize_processing']:
|
||||||
m = load_custom_processor(data['customize_processing'])
|
m = load_custom_processor(data['customize_processing'])
|
||||||
@@ -635,13 +722,20 @@ def handle_result(args: List[str], data: Dict[str, Any], target_window_id: int,
|
|||||||
w = boss.window_id_map.get(target_window_id)
|
w = boss.window_id_map.get(target_window_id)
|
||||||
if w is not None:
|
if w is not None:
|
||||||
cwd = w.cwd_of_child
|
cwd = w.cwd_of_child
|
||||||
|
if w is None:
|
||||||
|
w = boss.active_window
|
||||||
program = None if program == 'default' else program
|
program = None if program == 'default' else program
|
||||||
for m, groupdict in zip(matches, groupdicts):
|
if text_type == 'hyperlink':
|
||||||
if groupdict:
|
for m in matches:
|
||||||
m = []
|
if w is not None:
|
||||||
for k, v in groupdict.items():
|
w.open_url(m, hyperlink_id=1, cwd=cwd)
|
||||||
m.append('{}={}'.format(k, v or ''))
|
else:
|
||||||
boss.open_url(m, program, cwd=cwd)
|
for m, groupdict in zip(matches, groupdicts):
|
||||||
|
if groupdict:
|
||||||
|
m = []
|
||||||
|
for k, v in groupdict.items():
|
||||||
|
m.append('{}={}'.format(k, v or ''))
|
||||||
|
boss.open_url(m, program, cwd=cwd)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
0
kittens/hyperlinked_grep/__init__.py
Normal file
0
kittens/hyperlinked_grep/__init__.py
Normal file
65
kittens/hyperlinked_grep/main.py
Normal file
65
kittens/hyperlinked_grep/main.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
|
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import signal
|
||||||
|
import socket
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from typing import Callable, cast
|
||||||
|
from urllib.parse import quote_from_bytes
|
||||||
|
|
||||||
|
|
||||||
|
def write_hyperlink(write: Callable[[bytes], None], url: bytes, line: bytes, frag: bytes = b'') -> None:
|
||||||
|
text = b'\033]8;;' + url
|
||||||
|
if frag:
|
||||||
|
text += b'#' + frag
|
||||||
|
text += b'\033\\' + line + b'\033]8;;\033\\'
|
||||||
|
write(text)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
if not sys.stdout.isatty() and '--pretty' not in sys.argv:
|
||||||
|
os.execlp('rg', 'rg', *sys.argv[1:])
|
||||||
|
cmdline = ['rg', '--pretty', '--with-filename'] + sys.argv[1:]
|
||||||
|
p = subprocess.Popen(cmdline, stdout=subprocess.PIPE)
|
||||||
|
assert p.stdout is not None
|
||||||
|
write: Callable[[bytes], None] = cast(Callable[[bytes], None], sys.stdout.buffer.write)
|
||||||
|
sgr_pat = re.compile(br'\x1b\[.*?m')
|
||||||
|
osc_pat = re.compile(b'\x1b\\].*?\x1b\\\\')
|
||||||
|
num_pat = re.compile(b'^(\\d+):')
|
||||||
|
|
||||||
|
in_result: bytes = b''
|
||||||
|
hostname = socket.gethostname().encode('utf-8')
|
||||||
|
|
||||||
|
try:
|
||||||
|
for line in p.stdout:
|
||||||
|
line = osc_pat.sub(b'', line) # remove any existing hyperlinks
|
||||||
|
clean_line = sgr_pat.sub(b'', line).rstrip() # remove SGR formatting
|
||||||
|
if not clean_line:
|
||||||
|
in_result = b''
|
||||||
|
write(b'\n')
|
||||||
|
continue
|
||||||
|
if in_result:
|
||||||
|
m = num_pat.match(clean_line)
|
||||||
|
if m is not None:
|
||||||
|
write_hyperlink(write, in_result, line, frag=m.group(1))
|
||||||
|
else:
|
||||||
|
if line.strip():
|
||||||
|
path = quote_from_bytes(os.path.abspath(clean_line)).encode('utf-8')
|
||||||
|
in_result = b'file://' + hostname + path
|
||||||
|
write_hyperlink(write, in_result, line)
|
||||||
|
else:
|
||||||
|
write(line)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
p.send_signal(signal.SIGINT)
|
||||||
|
p.stdout.close()
|
||||||
|
except EOFError:
|
||||||
|
p.stdout.close()
|
||||||
|
raise SystemExit(p.wait())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -30,7 +30,7 @@ from ..tui.images import (
|
|||||||
ConvertFailed, GraphicsCommand, NoImageMagick, OpenFailed, convert, fsenc,
|
ConvertFailed, GraphicsCommand, NoImageMagick, OpenFailed, convert, fsenc,
|
||||||
identify
|
identify
|
||||||
)
|
)
|
||||||
from ..tui.operations import clear_images_on_screen
|
from ..tui.operations import clear_images_on_screen, raw_mode
|
||||||
|
|
||||||
OPTIONS = '''\
|
OPTIONS = '''\
|
||||||
--align
|
--align
|
||||||
@@ -110,6 +110,11 @@ default=0
|
|||||||
Z-index of the image. When negative, text will be displayed on top of the image. Use
|
Z-index of the image. When negative, text will be displayed on top of the image. Use
|
||||||
a double minus for values under the threshold for drawing images under cell background
|
a double minus for values under the threshold for drawing images under cell background
|
||||||
colors. For example, --1 evaluates as -1,073,741,825.
|
colors. For example, --1 evaluates as -1,073,741,825.
|
||||||
|
|
||||||
|
|
||||||
|
--hold
|
||||||
|
type=bool-set
|
||||||
|
Wait for keypress before exiting after displaying the images.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
@@ -473,11 +478,14 @@ def main(args: List[str] = sys.argv) -> None:
|
|||||||
errors.append(e)
|
errors.append(e)
|
||||||
if parsed_opts.place:
|
if parsed_opts.place:
|
||||||
sys.stdout.buffer.write(b'\0338') # restore cursor
|
sys.stdout.buffer.write(b'\0338') # restore cursor
|
||||||
if not errors:
|
if errors:
|
||||||
return
|
for err in errors:
|
||||||
for err in errors:
|
print(err, file=sys.stderr)
|
||||||
print(err, file=sys.stderr)
|
if cli_opts.hold:
|
||||||
raise SystemExit(1)
|
with open(os.ctermid()) as tty:
|
||||||
|
with raw_mode(tty.fileno()):
|
||||||
|
tty.buffer.read(1)
|
||||||
|
raise SystemExit(1 if errors else 0)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ from typing import Callable, Dict, List, Tuple
|
|||||||
|
|
||||||
from kitty.cli import parse_args
|
from kitty.cli import parse_args
|
||||||
from kitty.cli_stub import PanelCLIOptions
|
from kitty.cli_stub import PanelCLIOptions
|
||||||
from kitty.options_stub import Options
|
|
||||||
from kitty.constants import is_macos
|
from kitty.constants import is_macos
|
||||||
|
from kitty.os_window_size import WindowSizeData
|
||||||
|
|
||||||
OPTIONS = r'''
|
OPTIONS = r'''
|
||||||
--lines
|
--lines
|
||||||
@@ -108,7 +108,7 @@ def setup_x11_window(win_id: int) -> None:
|
|||||||
func(win_id, window_width, window_height)
|
func(win_id, window_width, window_height)
|
||||||
|
|
||||||
|
|
||||||
def initial_window_size_func(opts: Options, cached_values: Dict) -> Callable[[int, int, float, float, float, float], Tuple[int, int]]:
|
def initial_window_size_func(opts: WindowSizeData, cached_values: Dict) -> Callable[[int, int, float, float, float, float], Tuple[int, int]]:
|
||||||
from kitty.fast_data_types import glfw_primary_monitor_size, set_smallest_allowed_resize
|
from kitty.fast_data_types import glfw_primary_monitor_size, set_smallest_allowed_resize
|
||||||
|
|
||||||
def initial_window_size(cell_width: int, cell_height: int, dpi_x: float, dpi_y: float, xscale: float, yscale: float) -> Tuple[int, int]:
|
def initial_window_size(cell_width: int, cell_height: int, dpi_x: float, dpi_y: float, xscale: float, yscale: float) -> Tuple[int, int]:
|
||||||
|
|||||||
0
kittens/remote_file/__init__.py
Normal file
0
kittens/remote_file/__init__.py
Normal file
325
kittens/remote_file/main.py
Normal file
325
kittens/remote_file/main.py
Normal file
@@ -0,0 +1,325 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
|
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import shlex
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
from typing import Any, List, Optional
|
||||||
|
|
||||||
|
from kitty.cli import parse_args
|
||||||
|
from kitty.cli_stub import RemoteFileCLIOptions
|
||||||
|
from kitty.constants import cache_dir
|
||||||
|
from kitty.typing import BossType
|
||||||
|
from kitty.utils import (
|
||||||
|
SSHConnectionData, command_for_open, get_editor, open_cmd
|
||||||
|
)
|
||||||
|
|
||||||
|
from ..tui.handler import result_handler
|
||||||
|
from ..tui.operations import (
|
||||||
|
faint, raw_mode, reset_terminal, styled
|
||||||
|
)
|
||||||
|
from ..tui.utils import get_key_press
|
||||||
|
|
||||||
|
|
||||||
|
def key(x: str) -> str:
|
||||||
|
return styled(x, bold=True, fg='green')
|
||||||
|
|
||||||
|
|
||||||
|
def option_text() -> str:
|
||||||
|
return '''\
|
||||||
|
--mode -m
|
||||||
|
choices=ask,edit
|
||||||
|
default=ask
|
||||||
|
Which mode to operate in.
|
||||||
|
|
||||||
|
|
||||||
|
--path -p
|
||||||
|
Path to the remote file.
|
||||||
|
|
||||||
|
|
||||||
|
--hostname -h
|
||||||
|
Hostname of the remote host.
|
||||||
|
|
||||||
|
|
||||||
|
--ssh-connection-data
|
||||||
|
The data used to connect over ssh.
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
def show_error(msg: str) -> None:
|
||||||
|
print(styled(msg, fg='red'))
|
||||||
|
print()
|
||||||
|
print('Press any key to exit...')
|
||||||
|
sys.stdout.flush()
|
||||||
|
with raw_mode():
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
q = sys.stdin.buffer.read(1)
|
||||||
|
if q:
|
||||||
|
break
|
||||||
|
except (KeyboardInterrupt, EOFError):
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def ask_action(opts: RemoteFileCLIOptions) -> str:
|
||||||
|
print('What would you like to do with the remote file on {}:'.format(styled(opts.hostname or 'unknown', bold=True, fg='magenta')))
|
||||||
|
print(styled(opts.path or '', fg='yellow', fg_intense=True))
|
||||||
|
print()
|
||||||
|
|
||||||
|
def help_text(x: str) -> str:
|
||||||
|
return faint(x)
|
||||||
|
|
||||||
|
print('{}dit the file'.format(key('E')))
|
||||||
|
print(help_text('The file will be downloaded and opened in an editor. Any changes you save will'
|
||||||
|
' be automatically sent back to the remote machine'))
|
||||||
|
print()
|
||||||
|
|
||||||
|
print('{}pen the file'.format(key('O')))
|
||||||
|
print(help_text('The file will be downloaded and opened by the default open program'))
|
||||||
|
print()
|
||||||
|
|
||||||
|
print('{}ave the file'.format(key('S')))
|
||||||
|
print(help_text('The file will be downloaded to a destination you select'))
|
||||||
|
print()
|
||||||
|
|
||||||
|
print('{}ancel'.format(key('C')))
|
||||||
|
print()
|
||||||
|
|
||||||
|
sys.stdout.flush()
|
||||||
|
response = get_key_press('ceos', 'c')
|
||||||
|
return {'e': 'edit', 'o': 'open', 's': 'save'}.get(response, 'cancel')
|
||||||
|
|
||||||
|
|
||||||
|
def hostname_matches(from_hyperlink: str, actual: str) -> bool:
|
||||||
|
if from_hyperlink == actual:
|
||||||
|
return True
|
||||||
|
if from_hyperlink.partition('.')[0] == actual.partition('.')[0]:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class ControlMaster:
|
||||||
|
|
||||||
|
def __init__(self, conn_data: SSHConnectionData, remote_path: str, cli_opts: RemoteFileCLIOptions, dest: str = ''):
|
||||||
|
self.conn_data = conn_data
|
||||||
|
self.cli_opts = cli_opts
|
||||||
|
self.remote_path = remote_path
|
||||||
|
self.dest = dest
|
||||||
|
self.tdir = ''
|
||||||
|
self.cmd_prefix = cmd = [
|
||||||
|
conn_data.binary, '-o', f'ControlPath=~/.ssh/kitty-master-{os.getpid()}-%r@%h:%p',
|
||||||
|
'-o', 'TCPKeepAlive=yes', '-o', 'ControlPersist=yes'
|
||||||
|
]
|
||||||
|
if conn_data.port:
|
||||||
|
cmd += ['-p', str(conn_data.port)]
|
||||||
|
self.batch_cmd_prefix = cmd + ['-o', 'BatchMode=yes']
|
||||||
|
|
||||||
|
def __enter__(self) -> 'ControlMaster':
|
||||||
|
subprocess.check_call(
|
||||||
|
self.cmd_prefix + ['-o', 'ControlMaster=auto', '-fN', self.conn_data.hostname])
|
||||||
|
subprocess.check_call(
|
||||||
|
self.batch_cmd_prefix + ['-O', 'check', self.conn_data.hostname])
|
||||||
|
if not self.dest:
|
||||||
|
self.tdir = tempfile.mkdtemp()
|
||||||
|
self.dest = os.path.join(self.tdir, os.path.basename(self.remote_path))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, *a: Any) -> bool:
|
||||||
|
subprocess.Popen(
|
||||||
|
self.batch_cmd_prefix + ['-O', 'exit', self.conn_data.hostname],
|
||||||
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL
|
||||||
|
).wait()
|
||||||
|
if self.tdir:
|
||||||
|
shutil.rmtree(self.tdir)
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_alive(self) -> bool:
|
||||||
|
return subprocess.Popen(
|
||||||
|
self.batch_cmd_prefix + ['-O', 'check', self.conn_data.hostname],
|
||||||
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL
|
||||||
|
).wait() == 0
|
||||||
|
|
||||||
|
def check_hostname_matches(self) -> bool:
|
||||||
|
cp = subprocess.run(self.batch_cmd_prefix + [self.conn_data.hostname, 'hostname', '-f'], stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL)
|
||||||
|
if cp.returncode == 0:
|
||||||
|
q = cp.stdout.decode('utf-8').strip()
|
||||||
|
if not hostname_matches(self.cli_opts.hostname or '', q):
|
||||||
|
print(reset_terminal(), end='')
|
||||||
|
print(f'The remote hostname {styled(q, fg="green")} does not match the')
|
||||||
|
print(f'hostname in the hyperlink {styled(self.cli_opts.hostname or "", fg="red")}')
|
||||||
|
print('This indicates that kitty has not connected to the correct remote machine.')
|
||||||
|
print('This can happen, for example, when using nested SSH sessions.')
|
||||||
|
print(f'The hostname kitty used to connect was: {styled(self.conn_data.hostname, fg="yellow")}', end='')
|
||||||
|
if self.conn_data.port is not None:
|
||||||
|
print(f' with port: {self.conn_data.port}')
|
||||||
|
print()
|
||||||
|
print()
|
||||||
|
print('Do you want to continue anyway?')
|
||||||
|
print(
|
||||||
|
f'{styled("Y", fg="green")}es',
|
||||||
|
f'{styled("N", fg="red")}o', sep='\t'
|
||||||
|
)
|
||||||
|
sys.stdout.flush()
|
||||||
|
response = get_key_press('yn', 'n')
|
||||||
|
print(reset_terminal(), end='')
|
||||||
|
return response == 'y'
|
||||||
|
return True
|
||||||
|
|
||||||
|
def download(self) -> bool:
|
||||||
|
with open(self.dest, 'wb') as f:
|
||||||
|
return subprocess.run(
|
||||||
|
self.batch_cmd_prefix + [self.conn_data.hostname, 'cat', self.remote_path],
|
||||||
|
stdout=f, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL
|
||||||
|
).returncode == 0
|
||||||
|
|
||||||
|
def upload(self, suppress_output: bool = True) -> bool:
|
||||||
|
cmd_prefix = self.cmd_prefix if suppress_output else self.batch_cmd_prefix
|
||||||
|
cmd = cmd_prefix + [self.conn_data.hostname, 'cat', '>', self.remote_path]
|
||||||
|
if not suppress_output:
|
||||||
|
print(' '.join(map(shlex.quote, cmd)))
|
||||||
|
redirect = subprocess.DEVNULL if suppress_output else None
|
||||||
|
with open(self.dest, 'rb') as f:
|
||||||
|
return subprocess.run(cmd, stdout=redirect, stderr=redirect, stdin=f).returncode == 0
|
||||||
|
|
||||||
|
|
||||||
|
Result = Optional[str]
|
||||||
|
|
||||||
|
|
||||||
|
def main(args: List[str]) -> Result:
|
||||||
|
msg = 'Ask the user what to do with the remote file'
|
||||||
|
try:
|
||||||
|
cli_opts, items = parse_args(args[1:], option_text, '', msg, 'kitty remote_file', result_class=RemoteFileCLIOptions)
|
||||||
|
except SystemExit as e:
|
||||||
|
if e.code != 0:
|
||||||
|
print(e.args[0])
|
||||||
|
input('Press enter to quit...')
|
||||||
|
raise SystemExit(e.code)
|
||||||
|
|
||||||
|
try:
|
||||||
|
action = ask_action(cli_opts)
|
||||||
|
finally:
|
||||||
|
print(reset_terminal(), end='', flush=True)
|
||||||
|
try:
|
||||||
|
return handle_action(action, cli_opts)
|
||||||
|
except Exception:
|
||||||
|
print(reset_terminal(), end='', flush=True)
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
show_error('Failed with unhandled exception')
|
||||||
|
|
||||||
|
|
||||||
|
def save_as(conn_data: SSHConnectionData, remote_path: str, cli_opts: RemoteFileCLIOptions) -> None:
|
||||||
|
ddir = cache_dir()
|
||||||
|
os.makedirs(ddir, exist_ok=True)
|
||||||
|
last_used_store_path = os.path.join(ddir, 'remote-file-last-used.txt')
|
||||||
|
try:
|
||||||
|
with open(last_used_store_path) as f:
|
||||||
|
last_used_path = f.read()
|
||||||
|
except FileNotFoundError:
|
||||||
|
last_used_path = tempfile.gettempdir()
|
||||||
|
last_used_file = os.path.join(last_used_path, os.path.basename(remote_path))
|
||||||
|
print(
|
||||||
|
'Where do you wish to save the file? Leaving it blank will save it as:',
|
||||||
|
styled(last_used_file, fg='yellow')
|
||||||
|
)
|
||||||
|
print('Relative paths will be resolved from:', styled(os.getcwd(), fg_intense=True, bold=True))
|
||||||
|
print()
|
||||||
|
from ..tui.path_completer import PathCompleter
|
||||||
|
try:
|
||||||
|
dest = PathCompleter().input()
|
||||||
|
except (KeyboardInterrupt, EOFError):
|
||||||
|
return
|
||||||
|
if dest:
|
||||||
|
dest = os.path.expandvars(os.path.expanduser(dest))
|
||||||
|
if os.path.isdir(dest):
|
||||||
|
dest = os.path.join(dest, os.path.basename(remote_path))
|
||||||
|
with open(last_used_store_path, 'w') as f:
|
||||||
|
f.write(os.path.dirname(os.path.abspath(dest)))
|
||||||
|
else:
|
||||||
|
dest = last_used_file
|
||||||
|
if os.path.exists(dest):
|
||||||
|
print(reset_terminal(), end='')
|
||||||
|
print(f'The file {styled(dest, fg="yellow")} already exists. What would you like to do?')
|
||||||
|
print(f'{key("O")}verwrite {key("A")}bort Auto {key("R")}ename {key("N")}ew name')
|
||||||
|
response = get_key_press('anor', 'a')
|
||||||
|
if response == 'a':
|
||||||
|
return
|
||||||
|
if response == 'n':
|
||||||
|
print(reset_terminal(), end='')
|
||||||
|
return save_as(conn_data, remote_path, cli_opts)
|
||||||
|
|
||||||
|
if response == 'r':
|
||||||
|
q = dest
|
||||||
|
c = 0
|
||||||
|
while os.path.exists(q):
|
||||||
|
c += 1
|
||||||
|
b, ext = os.path.splitext(dest)
|
||||||
|
q = f'{b}-{c}{ext}'
|
||||||
|
dest = q
|
||||||
|
if os.path.dirname(dest):
|
||||||
|
os.makedirs(os.path.dirname(dest), exist_ok=True)
|
||||||
|
with ControlMaster(conn_data, remote_path, cli_opts, dest=dest) as master:
|
||||||
|
if master.check_hostname_matches():
|
||||||
|
if not master.download():
|
||||||
|
show_error('Failed to copy file from remote machine')
|
||||||
|
|
||||||
|
|
||||||
|
def handle_action(action: str, cli_opts: RemoteFileCLIOptions) -> Result:
|
||||||
|
conn_data = SSHConnectionData(*json.loads(cli_opts.ssh_connection_data or ''))
|
||||||
|
remote_path = cli_opts.path or ''
|
||||||
|
if action == 'open':
|
||||||
|
print('Opening', cli_opts.path, 'from', cli_opts.hostname)
|
||||||
|
dest = os.path.join(tempfile.mkdtemp(), os.path.basename(remote_path))
|
||||||
|
with ControlMaster(conn_data, remote_path, cli_opts, dest=dest) as master:
|
||||||
|
if master.check_hostname_matches():
|
||||||
|
if master.download():
|
||||||
|
return dest
|
||||||
|
show_error('Failed to copy file from remote machine')
|
||||||
|
elif action == 'edit':
|
||||||
|
print('Editing', cli_opts.path, 'from', cli_opts.hostname)
|
||||||
|
with ControlMaster(conn_data, remote_path, cli_opts) as master:
|
||||||
|
if not master.check_hostname_matches():
|
||||||
|
return None
|
||||||
|
if not master.download():
|
||||||
|
show_error(f'Failed to download {remote_path}')
|
||||||
|
return None
|
||||||
|
mtime = os.path.getmtime(master.dest)
|
||||||
|
print(reset_terminal(), end='', flush=True)
|
||||||
|
editor = get_editor()
|
||||||
|
editor_process = subprocess.Popen(editor + [master.dest])
|
||||||
|
while editor_process.poll() is None:
|
||||||
|
time.sleep(0.1)
|
||||||
|
newmtime = os.path.getmtime(master.dest)
|
||||||
|
if newmtime > mtime:
|
||||||
|
mtime = newmtime
|
||||||
|
if master.is_alive:
|
||||||
|
master.upload()
|
||||||
|
print(reset_terminal(), end='', flush=True)
|
||||||
|
if master.is_alive:
|
||||||
|
if not master.upload(suppress_output=False):
|
||||||
|
show_error(f'Failed to upload {remote_path}')
|
||||||
|
else:
|
||||||
|
show_error(f'Failed to upload {remote_path}, SSH master process died')
|
||||||
|
elif action == 'save':
|
||||||
|
print('Saving', cli_opts.path, 'from', cli_opts.hostname)
|
||||||
|
save_as(conn_data, remote_path, cli_opts)
|
||||||
|
|
||||||
|
|
||||||
|
@result_handler()
|
||||||
|
def handle_result(args: List[str], data: Result, target_window_id: int, boss: BossType) -> None:
|
||||||
|
if data:
|
||||||
|
cmd = command_for_open(boss.opts.open_url_with)
|
||||||
|
open_cmd(cmd, data)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(sys.argv)
|
||||||
@@ -7,7 +7,10 @@ import re
|
|||||||
import shlex
|
import shlex
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from typing import List, NoReturn, Set, Tuple
|
from contextlib import suppress
|
||||||
|
from typing import List, NoReturn, Optional, Set, Tuple
|
||||||
|
|
||||||
|
from kitty.utils import SSHConnectionData
|
||||||
|
|
||||||
SHELL_SCRIPT = '''\
|
SHELL_SCRIPT = '''\
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
@@ -60,6 +63,41 @@ def get_ssh_cli() -> Tuple[Set[str], Set[str]]:
|
|||||||
return set('-' + x for x in boolean_ssh_args), set('-' + x for x in other_ssh_args)
|
return set('-' + x for x in boolean_ssh_args), set('-' + x for x in other_ssh_args)
|
||||||
|
|
||||||
|
|
||||||
|
def get_connection_data(args: List[str]) -> Optional[SSHConnectionData]:
|
||||||
|
boolean_ssh_args, other_ssh_args = get_ssh_cli()
|
||||||
|
found_ssh = ''
|
||||||
|
port: Optional[int] = None
|
||||||
|
expecting_port = False
|
||||||
|
expecting_option_val = False
|
||||||
|
|
||||||
|
for i, arg in enumerate(args):
|
||||||
|
if not found_ssh:
|
||||||
|
if os.path.basename(arg).lower() in ('ssh', 'ssh.exe'):
|
||||||
|
found_ssh = arg
|
||||||
|
continue
|
||||||
|
if arg.startswith('-') and not expecting_option_val:
|
||||||
|
if arg in boolean_ssh_args:
|
||||||
|
continue
|
||||||
|
if arg.startswith('-p'):
|
||||||
|
if arg[2:].isdigit():
|
||||||
|
with suppress(Exception):
|
||||||
|
port = int(arg[2:])
|
||||||
|
elif arg == '-p':
|
||||||
|
expecting_port = True
|
||||||
|
expecting_option_val = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
if expecting_option_val:
|
||||||
|
if expecting_port:
|
||||||
|
with suppress(Exception):
|
||||||
|
port = int(arg)
|
||||||
|
expecting_port = False
|
||||||
|
expecting_option_val = False
|
||||||
|
continue
|
||||||
|
|
||||||
|
return SSHConnectionData(found_ssh, arg, port)
|
||||||
|
|
||||||
|
|
||||||
def parse_ssh_args(args: List[str]) -> Tuple[List[str], List[str], bool]:
|
def parse_ssh_args(args: List[str]) -> Tuple[List[str], List[str], bool]:
|
||||||
boolean_ssh_args, other_ssh_args = get_ssh_cli()
|
boolean_ssh_args, other_ssh_args = get_ssh_cli()
|
||||||
passthrough_args = {'-' + x for x in 'Nnf'}
|
passthrough_args = {'-' + x for x in 'Nnf'}
|
||||||
|
|||||||
385
kittens/tui/dircolors.py
Normal file
385
kittens/tui/dircolors.py
Normal file
@@ -0,0 +1,385 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
|
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
import os
|
||||||
|
import stat
|
||||||
|
from contextlib import suppress
|
||||||
|
from typing import Dict, Generator, Optional, Tuple, Union
|
||||||
|
|
||||||
|
DEFAULT_DIRCOLORS = r"""# {{{
|
||||||
|
# Configuration file for dircolors, a utility to help you set the
|
||||||
|
# LS_COLORS environment variable used by GNU ls with the --color option.
|
||||||
|
# Copyright (C) 1996-2019 Free Software Foundation, Inc.
|
||||||
|
# Copying and distribution of this file, with or without modification,
|
||||||
|
# are permitted provided the copyright notice and this notice are preserved.
|
||||||
|
# The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the
|
||||||
|
# slackware version of dircolors) are recognized but ignored.
|
||||||
|
# Below are TERM entries, which can be a glob patterns, to match
|
||||||
|
# against the TERM environment variable to determine if it is colorizable.
|
||||||
|
TERM Eterm
|
||||||
|
TERM ansi
|
||||||
|
TERM *color*
|
||||||
|
TERM con[0-9]*x[0-9]*
|
||||||
|
TERM cons25
|
||||||
|
TERM console
|
||||||
|
TERM cygwin
|
||||||
|
TERM dtterm
|
||||||
|
TERM gnome
|
||||||
|
TERM hurd
|
||||||
|
TERM jfbterm
|
||||||
|
TERM konsole
|
||||||
|
TERM kterm
|
||||||
|
TERM linux
|
||||||
|
TERM linux-c
|
||||||
|
TERM mlterm
|
||||||
|
TERM putty
|
||||||
|
TERM rxvt*
|
||||||
|
TERM screen*
|
||||||
|
TERM st
|
||||||
|
TERM terminator
|
||||||
|
TERM tmux*
|
||||||
|
TERM vt100
|
||||||
|
TERM xterm*
|
||||||
|
# Below are the color init strings for the basic file types.
|
||||||
|
# One can use codes for 256 or more colors supported by modern terminals.
|
||||||
|
# The default color codes use the capabilities of an 8 color terminal
|
||||||
|
# with some additional attributes as per the following codes:
|
||||||
|
# Attribute codes:
|
||||||
|
# 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed
|
||||||
|
# Text color codes:
|
||||||
|
# 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white
|
||||||
|
# Background color codes:
|
||||||
|
# 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white
|
||||||
|
#NORMAL 00 # no color code at all
|
||||||
|
#FILE 00 # regular file: use no color at all
|
||||||
|
RESET 0 # reset to "normal" color
|
||||||
|
DIR 01;34 # directory
|
||||||
|
LINK 01;36 # symbolic link. (If you set this to 'target' instead of a
|
||||||
|
# numerical value, the color is as for the file pointed to.)
|
||||||
|
MULTIHARDLINK 00 # regular file with more than one link
|
||||||
|
FIFO 40;33 # pipe
|
||||||
|
SOCK 01;35 # socket
|
||||||
|
DOOR 01;35 # door
|
||||||
|
BLK 40;33;01 # block device driver
|
||||||
|
CHR 40;33;01 # character device driver
|
||||||
|
ORPHAN 40;31;01 # symlink to nonexistent file, or non-stat'able file ...
|
||||||
|
MISSING 00 # ... and the files they point to
|
||||||
|
SETUID 37;41 # file that is setuid (u+s)
|
||||||
|
SETGID 30;43 # file that is setgid (g+s)
|
||||||
|
CAPABILITY 30;41 # file with capability
|
||||||
|
STICKY_OTHER_WRITABLE 30;42 # dir that is sticky and other-writable (+t,o+w)
|
||||||
|
OTHER_WRITABLE 34;42 # dir that is other-writable (o+w) and not sticky
|
||||||
|
STICKY 37;44 # dir with the sticky bit set (+t) and not other-writable
|
||||||
|
# This is for files with execute permission:
|
||||||
|
EXEC 01;32
|
||||||
|
# List any file extensions like '.gz' or '.tar' that you would like ls
|
||||||
|
# to colorize below. Put the extension, a space, and the color init string.
|
||||||
|
# (and any comments you want to add after a '#')
|
||||||
|
# If you use DOS-style suffixes, you may want to uncomment the following:
|
||||||
|
#.cmd 01;32 # executables (bright green)
|
||||||
|
#.exe 01;32
|
||||||
|
#.com 01;32
|
||||||
|
#.btm 01;32
|
||||||
|
#.bat 01;32
|
||||||
|
# Or if you want to colorize scripts even if they do not have the
|
||||||
|
# executable bit actually set.
|
||||||
|
#.sh 01;32
|
||||||
|
#.csh 01;32
|
||||||
|
# archives or compressed (bright red)
|
||||||
|
.tar 01;31
|
||||||
|
.tgz 01;31
|
||||||
|
.arc 01;31
|
||||||
|
.arj 01;31
|
||||||
|
.taz 01;31
|
||||||
|
.lha 01;31
|
||||||
|
.lz4 01;31
|
||||||
|
.lzh 01;31
|
||||||
|
.lzma 01;31
|
||||||
|
.tlz 01;31
|
||||||
|
.txz 01;31
|
||||||
|
.tzo 01;31
|
||||||
|
.t7z 01;31
|
||||||
|
.zip 01;31
|
||||||
|
.z 01;31
|
||||||
|
.dz 01;31
|
||||||
|
.gz 01;31
|
||||||
|
.lrz 01;31
|
||||||
|
.lz 01;31
|
||||||
|
.lzo 01;31
|
||||||
|
.xz 01;31
|
||||||
|
.zst 01;31
|
||||||
|
.tzst 01;31
|
||||||
|
.bz2 01;31
|
||||||
|
.bz 01;31
|
||||||
|
.tbz 01;31
|
||||||
|
.tbz2 01;31
|
||||||
|
.tz 01;31
|
||||||
|
.deb 01;31
|
||||||
|
.rpm 01;31
|
||||||
|
.jar 01;31
|
||||||
|
.war 01;31
|
||||||
|
.ear 01;31
|
||||||
|
.sar 01;31
|
||||||
|
.rar 01;31
|
||||||
|
.alz 01;31
|
||||||
|
.ace 01;31
|
||||||
|
.zoo 01;31
|
||||||
|
.cpio 01;31
|
||||||
|
.7z 01;31
|
||||||
|
.rz 01;31
|
||||||
|
.cab 01;31
|
||||||
|
.wim 01;31
|
||||||
|
.swm 01;31
|
||||||
|
.dwm 01;31
|
||||||
|
.esd 01;31
|
||||||
|
# image formats
|
||||||
|
.jpg 01;35
|
||||||
|
.jpeg 01;35
|
||||||
|
.mjpg 01;35
|
||||||
|
.mjpeg 01;35
|
||||||
|
.gif 01;35
|
||||||
|
.bmp 01;35
|
||||||
|
.pbm 01;35
|
||||||
|
.pgm 01;35
|
||||||
|
.ppm 01;35
|
||||||
|
.tga 01;35
|
||||||
|
.xbm 01;35
|
||||||
|
.xpm 01;35
|
||||||
|
.tif 01;35
|
||||||
|
.tiff 01;35
|
||||||
|
.png 01;35
|
||||||
|
.svg 01;35
|
||||||
|
.svgz 01;35
|
||||||
|
.mng 01;35
|
||||||
|
.pcx 01;35
|
||||||
|
.mov 01;35
|
||||||
|
.mpg 01;35
|
||||||
|
.mpeg 01;35
|
||||||
|
.m2v 01;35
|
||||||
|
.mkv 01;35
|
||||||
|
.webm 01;35
|
||||||
|
.ogm 01;35
|
||||||
|
.mp4 01;35
|
||||||
|
.m4v 01;35
|
||||||
|
.mp4v 01;35
|
||||||
|
.vob 01;35
|
||||||
|
.qt 01;35
|
||||||
|
.nuv 01;35
|
||||||
|
.wmv 01;35
|
||||||
|
.asf 01;35
|
||||||
|
.rm 01;35
|
||||||
|
.rmvb 01;35
|
||||||
|
.flc 01;35
|
||||||
|
.avi 01;35
|
||||||
|
.fli 01;35
|
||||||
|
.flv 01;35
|
||||||
|
.gl 01;35
|
||||||
|
.dl 01;35
|
||||||
|
.xcf 01;35
|
||||||
|
.xwd 01;35
|
||||||
|
.yuv 01;35
|
||||||
|
.cgm 01;35
|
||||||
|
.emf 01;35
|
||||||
|
# https://wiki.xiph.org/MIME_Types_and_File_Extensions
|
||||||
|
.ogv 01;35
|
||||||
|
.ogx 01;35
|
||||||
|
# audio formats
|
||||||
|
.aac 00;36
|
||||||
|
.au 00;36
|
||||||
|
.flac 00;36
|
||||||
|
.m4a 00;36
|
||||||
|
.mid 00;36
|
||||||
|
.midi 00;36
|
||||||
|
.mka 00;36
|
||||||
|
.mp3 00;36
|
||||||
|
.mpc 00;36
|
||||||
|
.ogg 00;36
|
||||||
|
.ra 00;36
|
||||||
|
.wav 00;36
|
||||||
|
# https://wiki.xiph.org/MIME_Types_and_File_Extensions
|
||||||
|
.oga 00;36
|
||||||
|
.opus 00;36
|
||||||
|
.spx 00;36
|
||||||
|
.xspf 00;36
|
||||||
|
""" # }}}
|
||||||
|
|
||||||
|
# special file?
|
||||||
|
special_types = (
|
||||||
|
(stat.S_IFLNK, 'ln'), # symlink
|
||||||
|
(stat.S_IFIFO, 'pi'), # pipe (FIFO)
|
||||||
|
(stat.S_IFSOCK, 'so'), # socket
|
||||||
|
(stat.S_IFBLK, 'bd'), # block device
|
||||||
|
(stat.S_IFCHR, 'cd'), # character device
|
||||||
|
(stat.S_ISUID, 'su'), # setuid
|
||||||
|
(stat.S_ISGID, 'sg'), # setgid
|
||||||
|
)
|
||||||
|
|
||||||
|
CODE_MAP = {
|
||||||
|
'RESET': 'rs',
|
||||||
|
'DIR': 'di',
|
||||||
|
'LINK': 'ln',
|
||||||
|
'MULTIHARDLINK': 'mh',
|
||||||
|
'FIFO': 'pi',
|
||||||
|
'SOCK': 'so',
|
||||||
|
'DOOR': 'do',
|
||||||
|
'BLK': 'bd',
|
||||||
|
'CHR': 'cd',
|
||||||
|
'ORPHAN': 'or',
|
||||||
|
'MISSING': 'mi',
|
||||||
|
'SETUID': 'su',
|
||||||
|
'SETGID': 'sg',
|
||||||
|
'CAPABILITY': 'ca',
|
||||||
|
'STICKY_OTHER_WRITABLE': 'tw',
|
||||||
|
'OTHER_WRITABLE': 'ow',
|
||||||
|
'STICKY': 'st',
|
||||||
|
'EXEC': 'ex',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def stat_at(file: str, cwd: Optional[Union[int, str]] = None, follow_symlinks: bool = False) -> os.stat_result:
|
||||||
|
dirfd: Optional[int] = None
|
||||||
|
need_to_close = False
|
||||||
|
if isinstance(cwd, str):
|
||||||
|
dirfd = os.open(cwd, os.O_RDONLY)
|
||||||
|
need_to_close = True
|
||||||
|
elif isinstance(cwd, int):
|
||||||
|
dirfd = cwd
|
||||||
|
|
||||||
|
try:
|
||||||
|
return os.stat(file, dir_fd=dirfd, follow_symlinks=follow_symlinks)
|
||||||
|
finally:
|
||||||
|
if need_to_close and dirfd is not None:
|
||||||
|
os.close(dirfd)
|
||||||
|
|
||||||
|
|
||||||
|
class Dircolors:
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.codes: Dict[str, str] = {}
|
||||||
|
self.extensions: Dict[str, str] = {}
|
||||||
|
if not self.load_from_environ() and not self.load_from_file():
|
||||||
|
self.load_defaults()
|
||||||
|
|
||||||
|
def clear(self) -> None:
|
||||||
|
self.codes.clear()
|
||||||
|
self.extensions.clear()
|
||||||
|
|
||||||
|
def load_from_file(self) -> bool:
|
||||||
|
for candidate in (os.path.expanduser('~/.dir_colors'), '/etc/DIR_COLORS'):
|
||||||
|
with suppress(Exception):
|
||||||
|
with open(candidate) as f:
|
||||||
|
return self.load_from_dircolors(f.read())
|
||||||
|
return False
|
||||||
|
|
||||||
|
def load_from_lscolors(self, lscolors: str) -> bool:
|
||||||
|
self.clear()
|
||||||
|
if not lscolors:
|
||||||
|
return False
|
||||||
|
|
||||||
|
for item in lscolors.split(':'):
|
||||||
|
try:
|
||||||
|
code, color = item.split('=', 1)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
if code.startswith('*.'):
|
||||||
|
self.extensions[code[1:]] = color
|
||||||
|
else:
|
||||||
|
self.codes[code] = color
|
||||||
|
|
||||||
|
return bool(self.codes or self.extensions)
|
||||||
|
|
||||||
|
def load_from_environ(self, envvar: str = 'LS_COLORS') -> bool:
|
||||||
|
return self.load_from_lscolors(os.environ.get(envvar) or '')
|
||||||
|
|
||||||
|
def load_from_dircolors(self, database: str, strict: bool = False) -> bool:
|
||||||
|
self.clear()
|
||||||
|
|
||||||
|
for line in database.splitlines():
|
||||||
|
line = line.split('#')[0].strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
split = line.split()
|
||||||
|
if len(split) != 2:
|
||||||
|
if strict:
|
||||||
|
raise ValueError(f'Warning: unable to parse dircolors line "{line}"')
|
||||||
|
continue
|
||||||
|
|
||||||
|
key, val = split
|
||||||
|
if key == 'TERM':
|
||||||
|
continue
|
||||||
|
if key in CODE_MAP:
|
||||||
|
self.codes[CODE_MAP[key]] = val
|
||||||
|
elif key.startswith('.'):
|
||||||
|
self.extensions[key] = val
|
||||||
|
elif strict:
|
||||||
|
raise ValueError(f'Warning: unable to parse dircolors line "{line}"')
|
||||||
|
|
||||||
|
return bool(self.codes or self.extensions)
|
||||||
|
|
||||||
|
def load_defaults(self) -> bool:
|
||||||
|
self.clear()
|
||||||
|
return self.load_from_dircolors(DEFAULT_DIRCOLORS, True)
|
||||||
|
|
||||||
|
def generate_lscolors(self) -> str:
|
||||||
|
""" Output the database in the format used by the LS_COLORS environment variable. """
|
||||||
|
|
||||||
|
def gen_pairs() -> Generator[Tuple[str, str], None, None]:
|
||||||
|
for pair in self.codes.items():
|
||||||
|
yield pair
|
||||||
|
for pair in self.extensions.items():
|
||||||
|
# change .xyz to *.xyz
|
||||||
|
yield '*' + pair[0], pair[1]
|
||||||
|
|
||||||
|
return ':'.join('%s=%s' % pair for pair in gen_pairs())
|
||||||
|
|
||||||
|
def _format_code(self, text: str, code: str) -> str:
|
||||||
|
val = self.codes.get(code)
|
||||||
|
return '\033[%sm%s\033[%sm' % (val, text, self.codes.get('rs', '0')) if val else text
|
||||||
|
|
||||||
|
def _format_ext(self, text: str, ext: str) -> str:
|
||||||
|
val = self.extensions.get(ext, '0')
|
||||||
|
return '\033[%sm%s\033[%sm' % (val, text, self.codes.get('rs', '0')) if val else text
|
||||||
|
|
||||||
|
def format_mode(self, text: str, sr: os.stat_result) -> str:
|
||||||
|
mode = sr.st_mode
|
||||||
|
if stat.S_ISDIR(mode):
|
||||||
|
if (mode & (stat.S_ISVTX | stat.S_IWOTH)) == (stat.S_ISVTX | stat.S_IWOTH):
|
||||||
|
# sticky and world-writable
|
||||||
|
return self._format_code(text, 'tw')
|
||||||
|
if mode & stat.S_ISVTX:
|
||||||
|
# sticky but not world-writable
|
||||||
|
return self._format_code(text, 'st')
|
||||||
|
if mode & stat.S_IWOTH:
|
||||||
|
# world-writable but not sticky
|
||||||
|
return self._format_code(text, 'ow')
|
||||||
|
# normal directory
|
||||||
|
return self._format_code(text, 'di')
|
||||||
|
|
||||||
|
for mask, code in special_types:
|
||||||
|
if (mode & mask) == mask:
|
||||||
|
return self._format_code(text, code)
|
||||||
|
|
||||||
|
# executable file?
|
||||||
|
if mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH):
|
||||||
|
return self._format_code(text, 'ex')
|
||||||
|
|
||||||
|
# regular file, format according to its extension
|
||||||
|
ext = os.path.splitext(text)[1]
|
||||||
|
if ext:
|
||||||
|
return self._format_ext(text, ext)
|
||||||
|
return text
|
||||||
|
|
||||||
|
def __call__(self, path: str, text: str, cwd: Optional[Union[int, str]] = None) -> str:
|
||||||
|
follow_symlinks = self.codes.get('ln') == 'target'
|
||||||
|
try:
|
||||||
|
sr = stat_at(path, cwd, follow_symlinks)
|
||||||
|
except OSError:
|
||||||
|
return text
|
||||||
|
return self.format_mode(text, sr)
|
||||||
|
|
||||||
|
|
||||||
|
def develop() -> None:
|
||||||
|
import sys
|
||||||
|
print(Dircolors()(sys.argv[-1], sys.argv[-1]))
|
||||||
@@ -73,6 +73,11 @@ def clear_to_eol() -> str:
|
|||||||
return '\033[K'
|
return '\033[K'
|
||||||
|
|
||||||
|
|
||||||
|
@cmd
|
||||||
|
def reset_terminal() -> str:
|
||||||
|
return '\033]\033\\\033c'
|
||||||
|
|
||||||
|
|
||||||
@cmd
|
@cmd
|
||||||
def bell() -> str:
|
def bell() -> str:
|
||||||
return '\a'
|
return '\a'
|
||||||
@@ -288,6 +293,20 @@ def alternate_screen(f: Optional[IO[str]] = None) -> Generator[None, None, None]
|
|||||||
print(reset_mode('ALTERNATE_SCREEN'), end='', file=f)
|
print(reset_mode('ALTERNATE_SCREEN'), end='', file=f)
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def raw_mode(fd: Optional[int] = None) -> Generator[None, None, None]:
|
||||||
|
import tty
|
||||||
|
import termios
|
||||||
|
if fd is None:
|
||||||
|
fd = sys.stdin.fileno()
|
||||||
|
old = termios.tcgetattr(fd)
|
||||||
|
try:
|
||||||
|
tty.setraw(fd)
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
termios.tcsetattr(fd, termios.TCSADRAIN, old)
|
||||||
|
|
||||||
|
|
||||||
@cmd
|
@cmd
|
||||||
def set_default_colors(
|
def set_default_colors(
|
||||||
fg: Optional[Union[Color, str]] = None,
|
fg: Optional[Union[Color, str]] = None,
|
||||||
@@ -341,7 +360,7 @@ def request_from_clipboard(use_primary: bool = False) -> str:
|
|||||||
return '\x1b]52;{};?\x07'.format('p' if use_primary else 'c')
|
return '\x1b]52;{};?\x07'.format('p' if use_primary else 'c')
|
||||||
|
|
||||||
|
|
||||||
# Boilerplate to make operations availble via Handler.cmd {{{
|
# Boilerplate to make operations available via Handler.cmd {{{
|
||||||
|
|
||||||
|
|
||||||
def writer(handler: HandlerType, func: Callable) -> Callable:
|
def writer(handler: HandlerType, func: Callable) -> Callable:
|
||||||
|
|||||||
150
kittens/tui/path_completer.py
Normal file
150
kittens/tui/path_completer.py
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
|
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
from typing import Any, Callable, Dict, Generator, Optional, Sequence, Tuple
|
||||||
|
|
||||||
|
from kitty.fast_data_types import wcswidth
|
||||||
|
from kitty.utils import ScreenSize, screen_size_function
|
||||||
|
|
||||||
|
from .operations import styled
|
||||||
|
|
||||||
|
|
||||||
|
def directory_completions(path: str, qpath: str, prefix: str = '') -> Generator[str, None, None]:
|
||||||
|
try:
|
||||||
|
entries = os.scandir(qpath)
|
||||||
|
except OSError:
|
||||||
|
return
|
||||||
|
for x in entries:
|
||||||
|
try:
|
||||||
|
is_dir = x.is_dir()
|
||||||
|
except OSError:
|
||||||
|
is_dir = False
|
||||||
|
name = x.name + (os.sep if is_dir else '')
|
||||||
|
if not prefix or name.startswith(prefix):
|
||||||
|
if path:
|
||||||
|
yield os.path.join(path, name)
|
||||||
|
else:
|
||||||
|
yield name
|
||||||
|
|
||||||
|
|
||||||
|
def expand_path(path: str) -> str:
|
||||||
|
return os.path.abspath(os.path.expandvars(os.path.expanduser(path)))
|
||||||
|
|
||||||
|
|
||||||
|
def find_completions(path: str) -> Generator[str, None, None]:
|
||||||
|
if path and path[0] == '~':
|
||||||
|
if path == '~':
|
||||||
|
yield '~' + os.sep
|
||||||
|
return
|
||||||
|
if os.sep not in path:
|
||||||
|
qpath = os.path.expanduser(path)
|
||||||
|
if qpath != path:
|
||||||
|
yield path + os.sep
|
||||||
|
return
|
||||||
|
qpath = expand_path(path)
|
||||||
|
if not path or path.endswith(os.sep):
|
||||||
|
yield from directory_completions(path, qpath)
|
||||||
|
else:
|
||||||
|
yield from directory_completions(os.path.dirname(path), os.path.dirname(qpath), os.path.basename(qpath))
|
||||||
|
|
||||||
|
|
||||||
|
def print_table(items: Sequence[str], screen_size: ScreenSize, dir_colors: Callable[[str, str], str]) -> None:
|
||||||
|
max_width = 0
|
||||||
|
item_widths = {}
|
||||||
|
for item in items:
|
||||||
|
item_widths[item] = w = wcswidth(item)
|
||||||
|
max_width = max(w, max_width)
|
||||||
|
col_width = max_width + 2
|
||||||
|
num_of_cols = max(1, screen_size.cols // col_width)
|
||||||
|
cr = 0
|
||||||
|
at_start = False
|
||||||
|
for item in items:
|
||||||
|
w = item_widths[item]
|
||||||
|
left = col_width - w
|
||||||
|
print(dir_colors(expand_path(item), item), ' ' * left, sep='', end='')
|
||||||
|
at_start = False
|
||||||
|
cr = (cr + 1) % num_of_cols
|
||||||
|
if not cr:
|
||||||
|
print()
|
||||||
|
at_start = True
|
||||||
|
if not at_start:
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
class PathCompleter:
|
||||||
|
|
||||||
|
def __init__(self, prompt: str = '> '):
|
||||||
|
self.prompt = prompt
|
||||||
|
self.prompt_len = wcswidth(self.prompt)
|
||||||
|
|
||||||
|
def __enter__(self) -> 'PathCompleter':
|
||||||
|
import readline
|
||||||
|
|
||||||
|
from .dircolors import Dircolors
|
||||||
|
if 'libedit' in readline.__doc__:
|
||||||
|
readline.parse_and_bind("bind -e")
|
||||||
|
readline.parse_and_bind("bind '\t' rl_complete")
|
||||||
|
else:
|
||||||
|
readline.parse_and_bind('tab: complete')
|
||||||
|
readline.parse_and_bind('set colored-stats on')
|
||||||
|
readline.set_completer_delims(' \t\n`!@#$%^&*()-=+[{]}\\|;:\'",<>?')
|
||||||
|
readline.set_completion_display_matches_hook(self.format_completions)
|
||||||
|
self.original_completer = readline.get_completer()
|
||||||
|
readline.set_completer(self)
|
||||||
|
self.cache: Dict[str, Tuple[str, ...]] = {}
|
||||||
|
self.dircolors = Dircolors()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def format_completions(self, substitution: str, matches: Sequence[str], longest_match_length: int) -> None:
|
||||||
|
import readline
|
||||||
|
print()
|
||||||
|
files, dirs = [], []
|
||||||
|
for m in matches:
|
||||||
|
if m.endswith('/'):
|
||||||
|
if len(m) > 1:
|
||||||
|
m = m[:-1]
|
||||||
|
dirs.append(m)
|
||||||
|
else:
|
||||||
|
files.append(m)
|
||||||
|
|
||||||
|
ss = screen_size_function()()
|
||||||
|
if dirs:
|
||||||
|
print(styled('Directories', bold=True, fg_intense=True))
|
||||||
|
print_table(dirs, ss, self.dircolors)
|
||||||
|
if files:
|
||||||
|
print(styled('Files', bold=True, fg_intense=True))
|
||||||
|
print_table(files, ss, self.dircolors)
|
||||||
|
|
||||||
|
buf = readline.get_line_buffer()
|
||||||
|
x = readline.get_endidx()
|
||||||
|
buflen = wcswidth(buf)
|
||||||
|
print(self.prompt, buf, sep='', end='')
|
||||||
|
if x < buflen:
|
||||||
|
pos = x + self.prompt_len
|
||||||
|
print(f"\r\033[{pos}C", end='')
|
||||||
|
print(sep='', end='', flush=True)
|
||||||
|
|
||||||
|
def __call__(self, text: str, state: int) -> Optional[str]:
|
||||||
|
options = self.cache.get(text)
|
||||||
|
if options is None:
|
||||||
|
options = self.cache[text] = tuple(find_completions(text))
|
||||||
|
if options and state < len(options):
|
||||||
|
return options[state]
|
||||||
|
|
||||||
|
def __exit__(self, *a: Any) -> bool:
|
||||||
|
import readline
|
||||||
|
del self.cache
|
||||||
|
readline.set_completer(self.original_completer)
|
||||||
|
readline.set_completion_display_matches_hook()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def input(self) -> str:
|
||||||
|
with self:
|
||||||
|
return input(self.prompt)
|
||||||
|
|
||||||
|
|
||||||
|
def develop() -> None:
|
||||||
|
PathCompleter().input()
|
||||||
29
kittens/tui/utils.py
Normal file
29
kittens/tui/utils.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
|
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from contextlib import suppress
|
||||||
|
|
||||||
|
from .operations import raw_mode, set_cursor_visible
|
||||||
|
|
||||||
|
|
||||||
|
def get_key_press(allowed: str, default: str) -> str:
|
||||||
|
response = default
|
||||||
|
with raw_mode():
|
||||||
|
print(set_cursor_visible(False), end='', flush=True)
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
q = sys.stdin.buffer.read(1)
|
||||||
|
if q:
|
||||||
|
if q in b'\x1b\x03':
|
||||||
|
break
|
||||||
|
with suppress(Exception):
|
||||||
|
response = q.decode('utf-8').lower()
|
||||||
|
if response in allowed:
|
||||||
|
break
|
||||||
|
except (KeyboardInterrupt, EOFError):
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
print(set_cursor_visible(True), end='', flush=True)
|
||||||
|
return response
|
||||||
@@ -20,7 +20,7 @@ from kitty.config import cached_values_for
|
|||||||
from kitty.constants import config_dir
|
from kitty.constants import config_dir
|
||||||
from kitty.fast_data_types import is_emoji_presentation_base, wcswidth
|
from kitty.fast_data_types import is_emoji_presentation_base, wcswidth
|
||||||
from kitty.key_encoding import (
|
from kitty.key_encoding import (
|
||||||
CTRL, RELEASE, SHIFT, KeyEvent, enter_key, key_defs as K
|
CTRL, PRESS, RELEASE, SHIFT, KeyEvent, enter_key, key_defs as K
|
||||||
)
|
)
|
||||||
from kitty.typing import BossType
|
from kitty.typing import BossType
|
||||||
from kitty.utils import ScreenSize, get_editor
|
from kitty.utils import ScreenSize, get_editor
|
||||||
@@ -38,6 +38,8 @@ UP = K['UP']
|
|||||||
DOWN = K['DOWN']
|
DOWN = K['DOWN']
|
||||||
LEFT = K['LEFT']
|
LEFT = K['LEFT']
|
||||||
RIGHT = K['RIGHT']
|
RIGHT = K['RIGHT']
|
||||||
|
RIGHT_BRACKET = K['RIGHT_BRACKET']
|
||||||
|
LEFT_BRACKET = K['LEFT_BRACKET']
|
||||||
TAB = K['TAB']
|
TAB = K['TAB']
|
||||||
ESCAPE = K['ESCAPE']
|
ESCAPE = K['ESCAPE']
|
||||||
F1 = K['F1']
|
F1 = K['F1']
|
||||||
@@ -478,7 +480,7 @@ class UnicodeInput(Handler):
|
|||||||
return
|
return
|
||||||
if key_event is enter_key:
|
if key_event is enter_key:
|
||||||
self.quit_loop(0)
|
self.quit_loop(0)
|
||||||
elif key_event.type is RELEASE:
|
elif key_event.type is PRESS:
|
||||||
if not key_event.mods:
|
if not key_event.mods:
|
||||||
if key_event.key is ESCAPE:
|
if key_event.key is ESCAPE:
|
||||||
self.quit_loop(1)
|
self.quit_loop(1)
|
||||||
@@ -492,8 +494,8 @@ class UnicodeInput(Handler):
|
|||||||
self.switch_mode(FAVORITES)
|
self.switch_mode(FAVORITES)
|
||||||
elif key_event.key is F12 and self.mode is FAVORITES:
|
elif key_event.key is F12 and self.mode is FAVORITES:
|
||||||
self.edit_favorites()
|
self.edit_favorites()
|
||||||
elif key_event.mods == CTRL and key_event.key is TAB:
|
elif key_event.mods == CTRL and key_event.key in (TAB, RIGHT_BRACKET, LEFT_BRACKET):
|
||||||
self.next_mode()
|
self.next_mode(-1 if key_event.key is LEFT_BRACKET else 1)
|
||||||
elif key_event.mods == CTRL | SHIFT and key_event.key is TAB:
|
elif key_event.mods == CTRL | SHIFT and key_event.key is TAB:
|
||||||
self.next_mode(-1)
|
self.next_mode(-1)
|
||||||
|
|
||||||
|
|||||||
61717
kittens/unicode_input/names.h
generated
61717
kittens/unicode_input/names.h
generated
File diff suppressed because one or more lines are too long
@@ -69,6 +69,7 @@ class Borders:
|
|||||||
self.os_window_id = os_window_id
|
self.os_window_id = os_window_id
|
||||||
self.tab_id = tab_id
|
self.tab_id = tab_id
|
||||||
self.draw_active_borders = opts.active_border_color is not None
|
self.draw_active_borders = opts.active_border_color is not None
|
||||||
|
self.draw_minimal_borders = opts.draw_minimal_borders
|
||||||
|
|
||||||
def __call__(
|
def __call__(
|
||||||
self,
|
self,
|
||||||
@@ -88,29 +89,24 @@ class Borders:
|
|||||||
if groups:
|
if groups:
|
||||||
bw = groups[0].effective_border()
|
bw = groups[0].effective_border()
|
||||||
draw_borders = bw > 0 and draw_window_borders
|
draw_borders = bw > 0 and draw_window_borders
|
||||||
if draw_borders:
|
|
||||||
border_data = current_layout.resolve_borders(all_windows)
|
|
||||||
active_group = all_windows.active_group
|
active_group = all_windows.active_group
|
||||||
|
|
||||||
for i, wg in enumerate(groups):
|
for i, wg in enumerate(groups):
|
||||||
window_bg = wg.default_bg
|
window_bg = wg.default_bg
|
||||||
window_bg = (window_bg << 8) | BorderColor.window_bg
|
window_bg = (window_bg << 8) | BorderColor.window_bg
|
||||||
if draw_borders:
|
if draw_borders and not self.draw_minimal_borders:
|
||||||
# Draw the border rectangles
|
# Draw the border rectangles
|
||||||
if wg is active_group and self.draw_active_borders:
|
if wg is active_group and self.draw_active_borders:
|
||||||
color = BorderColor.active
|
color = BorderColor.active
|
||||||
else:
|
else:
|
||||||
color = BorderColor.bell if wg.needs_attention else BorderColor.inactive
|
color = BorderColor.bell if wg.needs_attention else BorderColor.inactive
|
||||||
try:
|
draw_edges(self.os_window_id, self.tab_id, (color, color, color, color), wg, borders=True)
|
||||||
colors = tuple(color if needed else window_bg for needed in next(border_data))
|
|
||||||
draw_edges(self.os_window_id, self.tab_id, colors, wg, borders=True)
|
|
||||||
except StopIteration:
|
|
||||||
pass
|
|
||||||
if not has_background_image:
|
if not has_background_image:
|
||||||
# Draw the background rectangles over the padding region
|
# Draw the background rectangles over the padding region
|
||||||
colors = window_bg, window_bg, window_bg, window_bg
|
colors = window_bg, window_bg, window_bg, window_bg
|
||||||
draw_edges(self.os_window_id, self.tab_id, colors, wg)
|
draw_edges(self.os_window_id, self.tab_id, colors, wg)
|
||||||
|
|
||||||
color = BorderColor.inactive
|
if self.draw_minimal_borders:
|
||||||
for (left, top, right, bottom) in current_layout.window_independent_borders(all_windows):
|
for border_line in current_layout.minimal_borders(all_windows):
|
||||||
add_borders_rect(self.os_window_id, self.tab_id, left, top, right, bottom, color)
|
left, top, right, bottom = border_line.edges
|
||||||
|
add_borders_rect(self.os_window_id, self.tab_id, left, top, right, bottom, border_line.color)
|
||||||
|
|||||||
138
kitty/boss.py
138
kitty/boss.py
@@ -19,7 +19,7 @@ from .cli import create_opts, parse_args
|
|||||||
from .cli_stub import CLIOptions
|
from .cli_stub import CLIOptions
|
||||||
from .conf.utils import BadLine, to_cmdline
|
from .conf.utils import BadLine, to_cmdline
|
||||||
from .config import (
|
from .config import (
|
||||||
KeyAction, SubSequenceMap, common_opts_as_dict, initial_window_size_func,
|
KeyAction, SubSequenceMap, common_opts_as_dict,
|
||||||
prepare_config_file_for_editing
|
prepare_config_file_for_editing
|
||||||
)
|
)
|
||||||
from .config_data import MINIMUM_FONT_SIZE
|
from .config_data import MINIMUM_FONT_SIZE
|
||||||
@@ -28,7 +28,7 @@ from .constants import (
|
|||||||
)
|
)
|
||||||
from .fast_data_types import (
|
from .fast_data_types import (
|
||||||
CLOSE_BEING_CONFIRMED, IMPERATIVE_CLOSE_REQUESTED, NO_CLOSE_REQUESTED,
|
CLOSE_BEING_CONFIRMED, IMPERATIVE_CLOSE_REQUESTED, NO_CLOSE_REQUESTED,
|
||||||
ChildMonitor, background_opacity_of, change_background_opacity,
|
ChildMonitor, add_timer, background_opacity_of, change_background_opacity,
|
||||||
change_os_window_state, cocoa_set_menubar_title, create_os_window,
|
change_os_window_state, cocoa_set_menubar_title, create_os_window,
|
||||||
current_application_quit_request, current_os_window, destroy_global_data,
|
current_application_quit_request, current_os_window, destroy_global_data,
|
||||||
focus_os_window, get_clipboard_string, global_font_size,
|
focus_os_window, get_clipboard_string, global_font_size,
|
||||||
@@ -39,9 +39,11 @@ from .fast_data_types import (
|
|||||||
)
|
)
|
||||||
from .keys import get_shortcut, shortcut_matches
|
from .keys import get_shortcut, shortcut_matches
|
||||||
from .layout.base import set_layout_options
|
from .layout.base import set_layout_options
|
||||||
|
from .notify import notification_activated
|
||||||
from .options_stub import Options
|
from .options_stub import Options
|
||||||
|
from .os_window_size import initial_window_size_func
|
||||||
from .rgb import Color, color_from_int
|
from .rgb import Color, color_from_int
|
||||||
from .session import Session, create_sessions
|
from .session import Session, create_sessions, get_os_window_sizing_data
|
||||||
from .tabs import (
|
from .tabs import (
|
||||||
SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager
|
SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager
|
||||||
)
|
)
|
||||||
@@ -49,24 +51,19 @@ from .typing import PopenType, TypedDict
|
|||||||
from .utils import (
|
from .utils import (
|
||||||
func_name, get_editor, get_primary_selection, is_path_in_temp_dir,
|
func_name, get_editor, get_primary_selection, is_path_in_temp_dir,
|
||||||
log_error, open_url, parse_address_spec, parse_uri_list,
|
log_error, open_url, parse_address_spec, parse_uri_list,
|
||||||
remove_socket_file, safe_print, set_primary_selection, single_instance,
|
platform_window_id, remove_socket_file, safe_print, set_primary_selection,
|
||||||
startup_notification_handler
|
single_instance, startup_notification_handler
|
||||||
)
|
)
|
||||||
from .window import MatchPatternType, Window
|
from .window import MatchPatternType, Window
|
||||||
|
|
||||||
|
|
||||||
class OSWindowDict(TypedDict):
|
class OSWindowDict(TypedDict):
|
||||||
id: int
|
id: int
|
||||||
|
platform_window_id: Optional[int]
|
||||||
is_focused: bool
|
is_focused: bool
|
||||||
tabs: List[TabDict]
|
tabs: List[TabDict]
|
||||||
|
|
||||||
|
|
||||||
def notification_activated(identifier: str) -> None:
|
|
||||||
if identifier == 'new-version':
|
|
||||||
from .update_check import notification_activated as do
|
|
||||||
do()
|
|
||||||
|
|
||||||
|
|
||||||
def listen_on(spec: str) -> int:
|
def listen_on(spec: str) -> int:
|
||||||
import socket
|
import socket
|
||||||
family, address, socket_path = parse_address_spec(spec)
|
family, address, socket_path = parse_address_spec(spec)
|
||||||
@@ -170,12 +167,18 @@ class Boss:
|
|||||||
if new_os_window_trigger is not None:
|
if new_os_window_trigger is not None:
|
||||||
self.keymap.pop(new_os_window_trigger, None)
|
self.keymap.pop(new_os_window_trigger, None)
|
||||||
if is_macos:
|
if is_macos:
|
||||||
from .fast_data_types import cocoa_set_notification_activated_callback
|
from .fast_data_types import (
|
||||||
|
cocoa_set_notification_activated_callback
|
||||||
|
)
|
||||||
cocoa_set_notification_activated_callback(notification_activated)
|
cocoa_set_notification_activated_callback(notification_activated)
|
||||||
|
|
||||||
def startup_first_child(self, os_window_id: Optional[int]) -> None:
|
def startup_first_child(self, os_window_id: Optional[int]) -> None:
|
||||||
|
from kitty.launch import load_watch_modules
|
||||||
startup_sessions = create_sessions(self.opts, self.args, default_session=self.opts.startup_session)
|
startup_sessions = create_sessions(self.opts, self.args, default_session=self.opts.startup_session)
|
||||||
|
watchers = load_watch_modules(self.args.watcher)
|
||||||
for startup_session in startup_sessions:
|
for startup_session in startup_sessions:
|
||||||
|
if watchers is not None and watchers.has_watchers:
|
||||||
|
startup_session.add_watchers_to_all_windows(watchers)
|
||||||
self.add_os_window(startup_session, os_window_id=os_window_id)
|
self.add_os_window(startup_session, os_window_id=os_window_id)
|
||||||
os_window_id = None
|
os_window_id = None
|
||||||
if self.args.start_as != 'normal':
|
if self.args.start_as != 'normal':
|
||||||
@@ -194,11 +197,11 @@ class Boss:
|
|||||||
startup_id: Optional[str] = None
|
startup_id: Optional[str] = None
|
||||||
) -> int:
|
) -> int:
|
||||||
if os_window_id is None:
|
if os_window_id is None:
|
||||||
opts_for_size = opts_for_size or getattr(startup_session, 'os_window_size', None) or self.opts
|
size_data = get_os_window_sizing_data(opts_for_size or self.opts, startup_session)
|
||||||
wclass = wclass or self.args.cls or appname
|
wclass = wclass or getattr(startup_session, 'os_window_class', None) or self.args.cls or appname
|
||||||
with startup_notification_handler(do_notify=startup_id is not None, startup_id=startup_id) as pre_show_callback:
|
with startup_notification_handler(do_notify=startup_id is not None, startup_id=startup_id) as pre_show_callback:
|
||||||
os_window_id = create_os_window(
|
os_window_id = create_os_window(
|
||||||
initial_window_size_func(opts_for_size, self.cached_values),
|
initial_window_size_func(size_data, self.cached_values),
|
||||||
pre_show_callback,
|
pre_show_callback,
|
||||||
self.args.title or appname, wname or self.args.name or wclass, wclass)
|
self.args.title or appname, wname or self.args.name or wclass, wclass)
|
||||||
tm = TabManager(os_window_id, self.opts, self.args, startup_session)
|
tm = TabManager(os_window_id, self.opts, self.args, startup_session)
|
||||||
@@ -212,6 +215,7 @@ class Boss:
|
|||||||
for os_window_id, tm in self.os_window_map.items():
|
for os_window_id, tm in self.os_window_map.items():
|
||||||
yield {
|
yield {
|
||||||
'id': os_window_id,
|
'id': os_window_id,
|
||||||
|
'platform_window_id': platform_window_id(os_window_id),
|
||||||
'is_focused': tm is active_tab_manager,
|
'is_focused': tm is active_tab_manager,
|
||||||
'tabs': list(tm.list_tabs(active_tab, active_window)),
|
'tabs': list(tm.list_tabs(active_tab, active_window)),
|
||||||
}
|
}
|
||||||
@@ -347,8 +351,10 @@ class Boss:
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
def remote_control(self, *args: str) -> None:
|
def remote_control(self, *args: str) -> None:
|
||||||
|
from .rc.base import (
|
||||||
|
PayloadGetter, command_for_name, parse_subcommand_cli
|
||||||
|
)
|
||||||
from .remote_control import parse_rc_args
|
from .remote_control import parse_rc_args
|
||||||
from .rc.base import command_for_name, parse_subcommand_cli, PayloadGetter
|
|
||||||
try:
|
try:
|
||||||
global_opts, items = parse_rc_args(['@'] + list(args))
|
global_opts, items = parse_rc_args(['@'] + list(args))
|
||||||
if not items:
|
if not items:
|
||||||
@@ -382,6 +388,8 @@ class Boss:
|
|||||||
args.directory = os.path.join(data['cwd'], args.directory)
|
args.directory = os.path.join(data['cwd'], args.directory)
|
||||||
for session in create_sessions(opts, args, respect_cwd=True):
|
for session in create_sessions(opts, args, respect_cwd=True):
|
||||||
os_window_id = self.add_os_window(session, wclass=args.cls, wname=args.name, opts_for_size=opts, startup_id=startup_id)
|
os_window_id = self.add_os_window(session, wclass=args.cls, wname=args.name, opts_for_size=opts, startup_id=startup_id)
|
||||||
|
if opts.background_opacity != self.opts.background_opacity:
|
||||||
|
self._set_os_window_background_opacity(os_window_id, opts.background_opacity)
|
||||||
if data.get('notify_on_os_window_death'):
|
if data.get('notify_on_os_window_death'):
|
||||||
self.os_window_death_actions[os_window_id] = partial(self.notify_on_os_window_death, data['notify_on_os_window_death'])
|
self.os_window_death_actions[os_window_id] = partial(self.notify_on_os_window_death, data['notify_on_os_window_death'])
|
||||||
else:
|
else:
|
||||||
@@ -442,8 +450,34 @@ class Boss:
|
|||||||
def close_tab(self, tab: Optional[Tab] = None) -> None:
|
def close_tab(self, tab: Optional[Tab] = None) -> None:
|
||||||
tab = tab or self.active_tab
|
tab = tab or self.active_tab
|
||||||
if tab:
|
if tab:
|
||||||
for window in tab:
|
self.confirm_tab_close(tab)
|
||||||
self.close_window(window)
|
|
||||||
|
def confirm_tab_close(self, tab: Tab) -> None:
|
||||||
|
windows = tuple(tab)
|
||||||
|
needs_confirmation = self.opts.confirm_os_window_close > 0 and len(windows) >= self.opts.confirm_os_window_close
|
||||||
|
if not needs_confirmation:
|
||||||
|
self.close_tab_no_confirm(tab)
|
||||||
|
return
|
||||||
|
self._run_kitten('ask', ['--type=yesno', '--message', _(
|
||||||
|
'Are you sure you want to close this tab, it has {}'
|
||||||
|
' windows running?').format(len(windows))],
|
||||||
|
window=tab.active_window,
|
||||||
|
custom_callback=partial(self.handle_close_tab_confirmation, tab.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle_close_tab_confirmation(self, tab_id: int, data: Dict[str, Any], *a: Any) -> None:
|
||||||
|
if data['response'] != 'y':
|
||||||
|
return
|
||||||
|
for tab in self.all_tabs:
|
||||||
|
if tab.id == tab_id:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
self.close_tab_no_confirm(tab)
|
||||||
|
|
||||||
|
def close_tab_no_confirm(self, tab: Tab) -> None:
|
||||||
|
for window in tab:
|
||||||
|
self.close_window(window)
|
||||||
|
|
||||||
def toggle_fullscreen(self) -> None:
|
def toggle_fullscreen(self) -> None:
|
||||||
toggle_fullscreen()
|
toggle_fullscreen()
|
||||||
@@ -851,6 +885,9 @@ class Boss:
|
|||||||
add_history='history' in type_of_input,
|
add_history='history' in type_of_input,
|
||||||
add_wrap_markers='screen' in type_of_input
|
add_wrap_markers='screen' in type_of_input
|
||||||
).encode('utf-8')
|
).encode('utf-8')
|
||||||
|
elif type_of_input == 'selection':
|
||||||
|
sel = self.data_for_at(which='@selection', window=w)
|
||||||
|
data = sel.encode('utf-8') if sel else None
|
||||||
elif type_of_input is None:
|
elif type_of_input is None:
|
||||||
data = None
|
data = None
|
||||||
else:
|
else:
|
||||||
@@ -858,9 +895,16 @@ class Boss:
|
|||||||
else:
|
else:
|
||||||
data = input_data if isinstance(input_data, bytes) else input_data.encode('utf-8')
|
data = input_data if isinstance(input_data, bytes) else input_data.encode('utf-8')
|
||||||
copts = common_opts_as_dict(self.opts)
|
copts = common_opts_as_dict(self.opts)
|
||||||
|
final_args: List[str] = []
|
||||||
|
for x in args:
|
||||||
|
if x == '@selection':
|
||||||
|
sel = self.data_for_at(which='@selection', window=w)
|
||||||
|
if sel:
|
||||||
|
x = sel
|
||||||
|
final_args.append(x)
|
||||||
overlay_window = tab.new_special_window(
|
overlay_window = tab.new_special_window(
|
||||||
SpecialWindow(
|
SpecialWindow(
|
||||||
[kitty_exe(), '+runpy', 'from kittens.runner import main; main()'] + args,
|
[kitty_exe(), '+runpy', 'from kittens.runner import main; main()'] + final_args,
|
||||||
stdin=data,
|
stdin=data,
|
||||||
env={
|
env={
|
||||||
'KITTY_COMMON_OPTS': json.dumps(copts),
|
'KITTY_COMMON_OPTS': json.dumps(copts),
|
||||||
@@ -890,6 +934,9 @@ class Boss:
|
|||||||
kargs = shlex.split(cmdline) if cmdline else []
|
kargs = shlex.split(cmdline) if cmdline else []
|
||||||
self._run_kitten(kitten, kargs)
|
self._run_kitten(kitten, kargs)
|
||||||
|
|
||||||
|
def run_kitten(self, kitten: str, *args: str) -> None:
|
||||||
|
self._run_kitten(kitten, args)
|
||||||
|
|
||||||
def on_kitten_finish(self, target_window_id: str, end_kitten: Callable, source_window: Window) -> None:
|
def on_kitten_finish(self, target_window_id: str, end_kitten: Callable, source_window: Window) -> None:
|
||||||
output = self.get_output(source_window, num_lines=None)
|
output = self.get_output(source_window, num_lines=None)
|
||||||
from kittens.runner import deserialize
|
from kittens.runner import deserialize
|
||||||
@@ -973,13 +1020,29 @@ class Boss:
|
|||||||
tab.set_active_window(window_id)
|
tab.set_active_window(window_id)
|
||||||
|
|
||||||
def open_url(self, url: str, program: Optional[Union[str, List[str]]] = None, cwd: Optional[str] = None) -> None:
|
def open_url(self, url: str, program: Optional[Union[str, List[str]]] = None, cwd: Optional[str] = None) -> None:
|
||||||
if url:
|
if not url:
|
||||||
if isinstance(program, str):
|
return
|
||||||
program = to_cmdline(program)
|
if isinstance(program, str):
|
||||||
|
program = to_cmdline(program)
|
||||||
|
found_action = False
|
||||||
|
if program is None:
|
||||||
|
from .open_actions import actions_for_url
|
||||||
|
actions = list(actions_for_url(url))
|
||||||
|
if actions:
|
||||||
|
found_action = True
|
||||||
|
self.dispatch_action(actions.pop(0))
|
||||||
|
if actions:
|
||||||
|
self.drain_actions(actions)
|
||||||
|
if not found_action:
|
||||||
open_url(url, program or self.opts.open_url_with, cwd=cwd)
|
open_url(url, program or self.opts.open_url_with, cwd=cwd)
|
||||||
|
|
||||||
def open_url_lines(self, lines: Iterable[str], program: Optional[Union[str, List[str]]] = None) -> None:
|
def drain_actions(self, actions: List) -> None:
|
||||||
self.open_url(''.join(lines), program)
|
|
||||||
|
def callback(timer_id: Optional[int]) -> None:
|
||||||
|
self.dispatch_action(actions.pop(0))
|
||||||
|
if actions:
|
||||||
|
self.drain_actions(actions)
|
||||||
|
add_timer(callback, 0, False)
|
||||||
|
|
||||||
def destroy(self) -> None:
|
def destroy(self) -> None:
|
||||||
self.shutting_down = True
|
self.shutting_down = True
|
||||||
@@ -1243,7 +1306,7 @@ class Boss:
|
|||||||
self._new_window(list(args), cwd_from=cwd_from)
|
self._new_window(list(args), cwd_from=cwd_from)
|
||||||
|
|
||||||
def launch(self, *args: str) -> None:
|
def launch(self, *args: str) -> None:
|
||||||
from kitty.launch import parse_launch_args, launch
|
from kitty.launch import launch, parse_launch_args
|
||||||
opts, args_ = parse_launch_args(args)
|
opts, args_ = parse_launch_args(args)
|
||||||
launch(self, opts, args_)
|
launch(self, opts, args_)
|
||||||
|
|
||||||
@@ -1316,7 +1379,9 @@ class Boss:
|
|||||||
log_error('Failed to process update check data {!r}, with error: {}'.format(raw, e))
|
log_error('Failed to process update check data {!r}, with error: {}'.format(raw, e))
|
||||||
|
|
||||||
def dbus_notification_callback(self, activated: bool, a: int, b: Union[int, str]) -> None:
|
def dbus_notification_callback(self, activated: bool, a: int, b: Union[int, str]) -> None:
|
||||||
from .notify import dbus_notification_created, dbus_notification_activated
|
from .notify import (
|
||||||
|
dbus_notification_activated, dbus_notification_created
|
||||||
|
)
|
||||||
if activated:
|
if activated:
|
||||||
assert isinstance(b, str)
|
assert isinstance(b, str)
|
||||||
dbus_notification_activated(a, b)
|
dbus_notification_activated(a, b)
|
||||||
@@ -1333,7 +1398,9 @@ class Boss:
|
|||||||
self.show_error(_('Errors in kitty.conf'), msg)
|
self.show_error(_('Errors in kitty.conf'), msg)
|
||||||
|
|
||||||
def set_colors(self, *args: str) -> None:
|
def set_colors(self, *args: str) -> None:
|
||||||
from kitty.rc.base import parse_subcommand_cli, command_for_name, PayloadGetter
|
from kitty.rc.base import (
|
||||||
|
PayloadGetter, command_for_name, parse_subcommand_cli
|
||||||
|
)
|
||||||
from kitty.remote_control import parse_rc_args
|
from kitty.remote_control import parse_rc_args
|
||||||
c = command_for_name('set_colors')
|
c = command_for_name('set_colors')
|
||||||
opts, items = parse_subcommand_cli(c, ['set-colors'] + list(args))
|
opts, items = parse_subcommand_cli(c, ['set-colors'] + list(args))
|
||||||
@@ -1495,3 +1562,20 @@ class Boss:
|
|||||||
set_background_image(path, os_windows, configured)
|
set_background_image(path, os_windows, configured)
|
||||||
for os_window_id in os_windows:
|
for os_window_id in os_windows:
|
||||||
self.default_bg_changed_for(os_window_id)
|
self.default_bg_changed_for(os_window_id)
|
||||||
|
|
||||||
|
def send_test_notification(self) -> None:
|
||||||
|
from time import monotonic
|
||||||
|
|
||||||
|
from .notify import notify
|
||||||
|
now = monotonic()
|
||||||
|
ident = f'test-notify-{now}'
|
||||||
|
notify(f'Test {now}', f'At: {now}', identifier=ident, subtitle=f'Test subtitle {now}')
|
||||||
|
|
||||||
|
def notification_activated(self, identifier: str, window_id: int, focus: bool, report: bool) -> None:
|
||||||
|
w = self.window_id_map.get(window_id)
|
||||||
|
if w is None:
|
||||||
|
return
|
||||||
|
if focus:
|
||||||
|
self.set_active_window(w, switch_os_window_if_needed=True)
|
||||||
|
if report:
|
||||||
|
w.report_notification_activated(identifier)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ static void (*parse_func)(Screen*, PyObject*, monotonic_t);
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
char *data;
|
char *data;
|
||||||
size_t sz;
|
size_t sz;
|
||||||
int fd;
|
id_type peer_id;
|
||||||
} Message;
|
} Message;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -71,9 +71,8 @@ static const Child EMPTY_CHILD = {0};
|
|||||||
pthread_mutex_##op(&screen->which##_buf_lock);
|
pthread_mutex_##op(&screen->which##_buf_lock);
|
||||||
#define children_mutex(op) \
|
#define children_mutex(op) \
|
||||||
pthread_mutex_##op(&children_lock);
|
pthread_mutex_##op(&children_lock);
|
||||||
#define peer_mutex(op) \
|
#define talk_mutex(op) \
|
||||||
pthread_mutex_##op(&talk_data.peer_lock);
|
pthread_mutex_##op(&talk_lock);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static Child children[MAX_CHILDREN] = {{0}};
|
static Child children[MAX_CHILDREN] = {{0}};
|
||||||
@@ -82,7 +81,7 @@ static Child add_queue[MAX_CHILDREN] = {{0}}, remove_queue[MAX_CHILDREN] = {{0}}
|
|||||||
static unsigned long remove_notify[MAX_CHILDREN] = {0};
|
static unsigned long remove_notify[MAX_CHILDREN] = {0};
|
||||||
static size_t add_queue_count = 0, remove_queue_count = 0;
|
static size_t add_queue_count = 0, remove_queue_count = 0;
|
||||||
static struct pollfd fds[MAX_CHILDREN + EXTRA_FDS] = {{0}};
|
static struct pollfd fds[MAX_CHILDREN + EXTRA_FDS] = {{0}};
|
||||||
static pthread_mutex_t children_lock;
|
static pthread_mutex_t children_lock, talk_lock;
|
||||||
static bool kill_signal_received = false;
|
static bool kill_signal_received = false;
|
||||||
static ChildMonitor *the_monitor = NULL;
|
static ChildMonitor *the_monitor = NULL;
|
||||||
static uint8_t drain_buf[1024];
|
static uint8_t drain_buf[1024];
|
||||||
@@ -131,6 +130,10 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
|||||||
PyErr_Format(PyExc_RuntimeError, "Failed to create children_lock mutex: %s", strerror(ret));
|
PyErr_Format(PyExc_RuntimeError, "Failed to create children_lock mutex: %s", strerror(ret));
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
if ((ret = pthread_mutex_init(&talk_lock, NULL)) != 0) {
|
||||||
|
PyErr_Format(PyExc_RuntimeError, "Failed to create talk_lock mutex: %s", strerror(ret));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
self = (ChildMonitor *)type->tp_alloc(type, 0);
|
self = (ChildMonitor *)type->tp_alloc(type, 0);
|
||||||
if (!init_loop_data(&self->io_loop_data)) return PyErr_SetFromErrno(PyExc_OSError);
|
if (!init_loop_data(&self->io_loop_data)) return PyErr_SetFromErrno(PyExc_OSError);
|
||||||
if (!install_signal_handlers(&self->io_loop_data)) return PyErr_SetFromErrno(PyExc_OSError);
|
if (!install_signal_handlers(&self->io_loop_data)) return PyErr_SetFromErrno(PyExc_OSError);
|
||||||
@@ -152,7 +155,13 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
dealloc(ChildMonitor* self) {
|
dealloc(ChildMonitor* self) {
|
||||||
|
if (self->messages) {
|
||||||
|
for (size_t i = 0; i < self->messages_count; i++) free(self->messages[i].data);
|
||||||
|
free(self->messages); self->messages = NULL;
|
||||||
|
self->messages_count = 0; self->messages_capacity = 0;
|
||||||
|
}
|
||||||
pthread_mutex_destroy(&children_lock);
|
pthread_mutex_destroy(&children_lock);
|
||||||
|
pthread_mutex_destroy(&talk_lock);
|
||||||
Py_CLEAR(self->dump_callback);
|
Py_CLEAR(self->dump_callback);
|
||||||
Py_CLEAR(self->death_notify);
|
Py_CLEAR(self->death_notify);
|
||||||
Py_TYPE(self)->tp_free((PyObject*)self);
|
Py_TYPE(self)->tp_free((PyObject*)self);
|
||||||
@@ -174,7 +183,7 @@ wakeup_io_loop(ChildMonitor *self, bool in_signal_handler) {
|
|||||||
|
|
||||||
static void* io_loop(void *data);
|
static void* io_loop(void *data);
|
||||||
static void* talk_loop(void *data);
|
static void* talk_loop(void *data);
|
||||||
static void send_response(int fd, const char *msg, size_t msg_sz);
|
static void send_response(id_type peer_id, const char *msg, size_t msg_sz);
|
||||||
static void wakeup_talk_loop(bool);
|
static void wakeup_talk_loop(bool);
|
||||||
static bool talk_thread_started = false;
|
static bool talk_thread_started = false;
|
||||||
|
|
||||||
@@ -227,7 +236,7 @@ schedule_write_to_child(unsigned long id, unsigned int num, ...) {
|
|||||||
va_list ap;
|
va_list ap;
|
||||||
va_start(ap, num);
|
va_start(ap, num);
|
||||||
for (unsigned int i = 0; i < num; i++) {
|
for (unsigned int i = 0; i < num; i++) {
|
||||||
data = va_arg(ap, const char*);
|
va_arg(ap, const char*);
|
||||||
sz += va_arg(ap, size_t);
|
sz += va_arg(ap, size_t);
|
||||||
}
|
}
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
@@ -324,7 +333,6 @@ parse_input(ChildMonitor *self) {
|
|||||||
size_t count = 0, remove_count = 0;
|
size_t count = 0, remove_count = 0;
|
||||||
bool input_read = false;
|
bool input_read = false;
|
||||||
monotonic_t now = monotonic();
|
monotonic_t now = monotonic();
|
||||||
PyObject *msg = NULL;
|
|
||||||
children_mutex(lock);
|
children_mutex(lock);
|
||||||
while (remove_queue_count) {
|
while (remove_queue_count) {
|
||||||
remove_queue_count--;
|
remove_queue_count--;
|
||||||
@@ -333,18 +341,6 @@ parse_input(ChildMonitor *self) {
|
|||||||
FREE_CHILD(remove_queue[remove_queue_count]);
|
FREE_CHILD(remove_queue[remove_queue_count]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (UNLIKELY(self->messages_count)) {
|
|
||||||
msg = PyTuple_New(self->messages_count);
|
|
||||||
if (msg) {
|
|
||||||
for (size_t i = 0; i < self->messages_count; i++) {
|
|
||||||
Message *m = self->messages + i;
|
|
||||||
PyTuple_SET_ITEM(msg, i, Py_BuildValue("y#i", m->data, (int)m->sz, m->fd));
|
|
||||||
free(m->data); m->data = NULL; m->sz = 0;
|
|
||||||
}
|
|
||||||
self->messages_count = 0;
|
|
||||||
} else fatal("Out of memory");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (UNLIKELY(kill_signal_received)) {
|
if (UNLIKELY(kill_signal_received)) {
|
||||||
global_state.quit_request = IMPERATIVE_CLOSE_REQUESTED;
|
global_state.quit_request = IMPERATIVE_CLOSE_REQUESTED;
|
||||||
global_state.has_pending_closes = true;
|
global_state.has_pending_closes = true;
|
||||||
@@ -358,15 +354,35 @@ parse_input(ChildMonitor *self) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
children_mutex(unlock);
|
children_mutex(unlock);
|
||||||
if (msg) {
|
|
||||||
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(msg); i++) {
|
Message *msgs = NULL;
|
||||||
PyObject *resp = PyObject_CallMethod(global_state.boss, "peer_message_received", "O", PyTuple_GET_ITEM(PyTuple_GET_ITEM(msg, i), 0));
|
size_t msgs_count = 0;
|
||||||
int peer_fd = (int)PyLong_AsLong(PyTuple_GET_ITEM(PyTuple_GET_ITEM(msg, i), 1));
|
talk_mutex(lock);
|
||||||
if (resp && PyBytes_Check(resp)) send_response(peer_fd, PyBytes_AS_STRING(resp), PyBytes_GET_SIZE(resp));
|
if (UNLIKELY(self->messages_count)) {
|
||||||
else { send_response(peer_fd, NULL, 0); if (!resp) PyErr_Print(); }
|
msgs = malloc(sizeof(Message) * self->messages_count);
|
||||||
|
if (msgs) {
|
||||||
|
memcpy(msgs, self->messages, sizeof(Message) * self->messages_count);
|
||||||
|
msgs_count = self->messages_count;
|
||||||
|
}
|
||||||
|
memset(self->messages, 0, sizeof(Message) * self->messages_capacity);
|
||||||
|
self->messages_count = 0;
|
||||||
|
}
|
||||||
|
talk_mutex(unlock);
|
||||||
|
|
||||||
|
if (msgs_count) {
|
||||||
|
for (size_t i = 0; i < msgs_count; i++) {
|
||||||
|
Message *msg = msgs + i;
|
||||||
|
PyObject *resp = NULL;
|
||||||
|
if (msg->data) {
|
||||||
|
resp = PyObject_CallMethod(global_state.boss, "peer_message_received", "y#", msg->data, (int)msg->sz);
|
||||||
|
free(msg->data);
|
||||||
|
if (!resp) PyErr_Print();
|
||||||
|
}
|
||||||
|
if (resp && PyBytes_Check(resp)) send_response(msg->peer_id, PyBytes_AS_STRING(resp), PyBytes_GET_SIZE(resp));
|
||||||
|
else send_response(msg->peer_id, NULL, 0);
|
||||||
Py_CLEAR(resp);
|
Py_CLEAR(resp);
|
||||||
}
|
}
|
||||||
Py_CLEAR(msg);
|
free(msgs); msgs = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
while(remove_count) {
|
while(remove_count) {
|
||||||
@@ -503,7 +519,7 @@ collect_cursor_info(CursorRenderInfo *ans, Window *w, monotonic_t now, OSWindow
|
|||||||
int d = monotonic_t_to_ms(OPT(cursor_blink_interval));
|
int d = monotonic_t_to_ms(OPT(cursor_blink_interval));
|
||||||
int n = t / d;
|
int n = t / d;
|
||||||
do_draw_cursor = n % 2 == 0 ? true : false;
|
do_draw_cursor = n % 2 == 0 ? true : false;
|
||||||
monotonic_t bucket = ms_to_monotonic_t((n + 1) * d);
|
monotonic_t bucket = ms_to_monotonic_t((monotonic_t)(n + 1) * d);
|
||||||
monotonic_t delay = bucket - time_since_start_blink;
|
monotonic_t delay = bucket - time_since_start_blink;
|
||||||
set_maximum_wait(delay);
|
set_maximum_wait(delay);
|
||||||
}
|
}
|
||||||
@@ -766,7 +782,7 @@ thread_write(void *x) {
|
|||||||
if (pos < data->sz) {
|
if (pos < data->sz) {
|
||||||
log_error("Failed to write all data to STDIN of child process with error: %s", strerror(errno));
|
log_error("Failed to write all data to STDIN of child process with error: %s", strerror(errno));
|
||||||
}
|
}
|
||||||
safe_close(data->fd);
|
safe_close(data->fd, __FILE__, __LINE__);
|
||||||
free_twd(data);
|
free_twd(data);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -783,7 +799,7 @@ cm_thread_write(PyObject UNUSED *self, PyObject *args) {
|
|||||||
data->fd = fd;
|
data->fd = fd;
|
||||||
memcpy(data->buf, buf, data->sz);
|
memcpy(data->buf, buf, data->sz);
|
||||||
int ret = pthread_create(&thread, NULL, thread_write, data);
|
int ret = pthread_create(&thread, NULL, thread_write, data);
|
||||||
if (ret != 0) { safe_close(fd); free_twd(data); return PyErr_SetFromErrno(PyExc_OSError); }
|
if (ret != 0) { safe_close(fd, __FILE__, __LINE__); free_twd(data); return PyErr_SetFromErrno(PyExc_OSError); }
|
||||||
pthread_detach(thread);
|
pthread_detach(thread);
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
@@ -1017,7 +1033,7 @@ hangup(pid_t pid) {
|
|||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
cleanup_child(ssize_t i) {
|
cleanup_child(ssize_t i) {
|
||||||
safe_close(children[i].fd);
|
safe_close(children[i].fd, __FILE__, __LINE__);
|
||||||
hangup(children[i].pid);
|
hangup(children[i].pid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1279,36 +1295,33 @@ io_loop(void *data) {
|
|||||||
// {{{ Talk thread functions
|
// {{{ Talk thread functions
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char *data;
|
id_type id;
|
||||||
size_t capacity, used;
|
size_t num_of_unresponded_messages_sent_to_main_thread;
|
||||||
|
bool finished_reading;
|
||||||
int fd;
|
int fd;
|
||||||
bool finished, close_socket, is_peer_command;
|
struct {
|
||||||
} PeerReadData;
|
char *data;
|
||||||
static PeerReadData empty_prd = {.fd = -1, 0};
|
size_t capacity, used, command_end;
|
||||||
|
bool finished;
|
||||||
|
} read;
|
||||||
|
struct {
|
||||||
|
char *data;
|
||||||
|
size_t capacity, used;
|
||||||
|
bool failed;
|
||||||
|
} write;
|
||||||
|
} Peer;
|
||||||
|
static id_type peer_id_counter = 0;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char *data;
|
size_t num_peers, peers_capacity;
|
||||||
size_t sz, pos;
|
Peer *peers;
|
||||||
int fd;
|
|
||||||
bool finished;
|
|
||||||
} PeerWriteData;
|
|
||||||
static PeerWriteData empty_pwd = {.fd = -1, 0};
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
size_t num_listen_fds, num_talk_fds, num_reads, num_writes, num_queued_writes;
|
|
||||||
size_t fds_capacity, reads_capacity, writes_capacity, queued_writes_capacity;
|
|
||||||
struct pollfd *fds;
|
|
||||||
PeerReadData *reads;
|
|
||||||
PeerWriteData *writes;
|
|
||||||
PeerWriteData *queued_writes;
|
|
||||||
LoopData loop_data;
|
LoopData loop_data;
|
||||||
pthread_mutex_t peer_lock;
|
|
||||||
} TalkData;
|
} TalkData;
|
||||||
|
|
||||||
static TalkData talk_data = {0};
|
static TalkData talk_data = {0};
|
||||||
|
|
||||||
typedef struct pollfd PollFD;
|
typedef struct pollfd PollFD;
|
||||||
#define PEER_LIMIT 256
|
#define PEER_LIMIT 256
|
||||||
#define nuke_socket(s) { shutdown(s, SHUT_RDWR); safe_close(s); }
|
#define nuke_socket(s) { shutdown(s, SHUT_RDWR); safe_close(s, __FILE__, __LINE__); }
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
accept_peer(int listen_fd, bool shutting_down) {
|
accept_peer(int listen_fd, bool shutting_down) {
|
||||||
@@ -1318,13 +1331,12 @@ accept_peer(int listen_fd, bool shutting_down) {
|
|||||||
if (!shutting_down) perror("accept() on talk socket failed!");
|
if (!shutting_down) perror("accept() on talk socket failed!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
size_t fd_idx = talk_data.num_listen_fds + talk_data.num_talk_fds;
|
if (talk_data.num_peers < PEER_LIMIT) {
|
||||||
if (fd_idx < PEER_LIMIT && talk_data.reads_capacity < PEER_LIMIT) {
|
ensure_space_for(&talk_data, peers, Peer, talk_data.num_peers + 8, peers_capacity, 8, false);
|
||||||
ensure_space_for(&talk_data, fds, PollFD, fd_idx + 1, fds_capacity, 8, false);
|
Peer *p = talk_data.peers + talk_data.num_peers++;
|
||||||
talk_data.fds[fd_idx].fd = peer; talk_data.fds[fd_idx].events = POLLIN;
|
memset(p, 0, sizeof(Peer));
|
||||||
ensure_space_for(&talk_data, reads, PeerReadData, talk_data.num_reads + 1, reads_capacity, 8, false);
|
p->fd = peer; p->id = ++peer_id_counter;
|
||||||
talk_data.reads[talk_data.num_reads] = empty_prd; talk_data.reads[talk_data.num_reads++].fd = peer;
|
if (!p->id) p->id = ++peer_id_counter;
|
||||||
talk_data.num_talk_fds++;
|
|
||||||
} else {
|
} else {
|
||||||
log_error("Too many peers want to talk, ignoring one.");
|
log_error("Too many peers want to talk, ignoring one.");
|
||||||
nuke_socket(peer);
|
nuke_socket(peer);
|
||||||
@@ -1332,148 +1344,102 @@ accept_peer(int listen_fd, bool shutting_down) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
free_peer(Peer *peer) {
|
||||||
|
free(peer->read.data); peer->read.data = NULL;
|
||||||
|
free(peer->write.data); peer->write.data = NULL;
|
||||||
|
if (peer->fd > -1) { nuke_socket(peer->fd); peer->fd = -1; }
|
||||||
|
}
|
||||||
|
|
||||||
#define KITTY_CMD_PREFIX "\x1bP@kitty-cmd{"
|
#define KITTY_CMD_PREFIX "\x1bP@kitty-cmd{"
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
queue_peer_message(ChildMonitor *self, char *buf, size_t sz, int fd) {
|
queue_peer_message(ChildMonitor *self, Peer *peer) {
|
||||||
children_mutex(lock);
|
talk_mutex(lock);
|
||||||
ensure_space_for(self, messages, Message, self->messages_count + 1, messages_capacity, 16, true);
|
ensure_space_for(self, messages, Message, self->messages_count + 16, messages_capacity, 16, true);
|
||||||
Message *m = self->messages + self->messages_count++;
|
Message *m = self->messages + self->messages_count++;
|
||||||
m->data = buf; m->sz = sz; m->fd = fd;
|
memset(m, 0, sizeof(Message));
|
||||||
children_mutex(unlock);
|
if (peer->read.used) {
|
||||||
|
m->data = malloc(peer->read.used);
|
||||||
|
if (m->data) {
|
||||||
|
memcpy(m->data, peer->read.data, peer->read.used);
|
||||||
|
m->sz = peer->read.used;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m->peer_id = peer->id;
|
||||||
|
peer->num_of_unresponded_messages_sent_to_main_thread++;
|
||||||
|
talk_mutex(unlock);
|
||||||
wakeup_main_loop();
|
wakeup_main_loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline bool
|
||||||
dispatch_peer_command(ChildMonitor *self, PeerReadData *rd, int fd) {
|
has_complete_peer_command(Peer *peer) {
|
||||||
size_t end = 0;
|
peer->read.command_end = 0;
|
||||||
for (size_t i = 0; i < rd->used - 1; i++) {
|
if (peer->read.used > sizeof(KITTY_CMD_PREFIX) && memcmp(peer->read.data, KITTY_CMD_PREFIX, sizeof(KITTY_CMD_PREFIX)-1) == 0) {
|
||||||
if (rd->data[i] == 0x1b && rd->data[i+1] == '\\') {
|
for (size_t i = sizeof(KITTY_CMD_PREFIX)-1; i < peer->read.used - 1; i++) {
|
||||||
end = i + 2;
|
if (peer->read.data[i] == 0x1b && peer->read.data[i+1] == '\\') {
|
||||||
break;
|
peer->read.command_end = i + 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!end) return;
|
return peer->read.command_end ? true : false;
|
||||||
char *buf = malloc(end + 8);
|
|
||||||
if (buf) {
|
|
||||||
memcpy(buf, rd->data, end);
|
|
||||||
queue_peer_message(self, buf, end, fd);
|
|
||||||
}
|
|
||||||
rd->is_peer_command = false;
|
|
||||||
if (rd->used > end) {
|
|
||||||
rd->used -= end;
|
|
||||||
memmove(rd->data, rd->data + end, rd->used);
|
|
||||||
if (rd->used >= sizeof(KITTY_CMD_PREFIX) - 1 && memcmp(rd->data, KITTY_CMD_PREFIX, sizeof(KITTY_CMD_PREFIX)-1) == 0) rd->is_peer_command = true;
|
|
||||||
} else rd->used = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
|
||||||
read_from_peer(ChildMonitor *self, int s) {
|
static inline void
|
||||||
bool read_finished = false;
|
dispatch_peer_command(ChildMonitor *self, Peer *peer) {
|
||||||
for (size_t i = 0; i < talk_data.num_reads; i++) {
|
if (peer->read.command_end) {
|
||||||
PeerReadData *rd = talk_data.reads + i;
|
size_t used = peer->read.used;
|
||||||
#define failed(msg) { read_finished = true; log_error("%s", msg); rd->finished = true; rd->close_socket = true; break; }
|
peer->read.used = peer->read.command_end;
|
||||||
if (rd->fd == s) {
|
queue_peer_message(self, peer);
|
||||||
if (rd->used >= rd->capacity) {
|
peer->read.used = used;
|
||||||
if (rd->capacity >= 64 * 1024) failed("Ignoring too large message from peer");
|
if (peer->read.used > peer->read.command_end) {
|
||||||
rd->capacity = MAX(8192u, rd->capacity * 2);
|
peer->read.used -= peer->read.command_end;
|
||||||
rd->data = realloc(rd->data, rd->capacity);
|
memmove(peer->read.data, peer->read.data + peer->read.command_end, peer->read.used);
|
||||||
if (!rd->data) failed("Out of memory");
|
} else peer->read.used = 0;
|
||||||
}
|
peer->read.command_end = 0;
|
||||||
ssize_t n = recv(s, rd->data + rd->used, rd->capacity - rd->used, 0);
|
}
|
||||||
if (n == 0) {
|
}
|
||||||
while (rd->is_peer_command) dispatch_peer_command(self, rd, s);
|
|
||||||
read_finished = true; rd->finished = true;
|
static inline void
|
||||||
if (rd->used) queue_peer_message(self, rd->data, rd->used, s);
|
read_from_peer(ChildMonitor *self, Peer *peer) {
|
||||||
else free(rd->data);
|
#define failed(msg) { log_error("Reading from peer failed: %s", msg); shutdown(peer->fd, SHUT_RD); peer->read.finished = true; return; }
|
||||||
rd->data = NULL;
|
if (peer->read.used >= peer->read.capacity) {
|
||||||
} else if (n < 0) {
|
if (peer->read.capacity >= 64 * 1024) failed("Ignoring too large message from peer");
|
||||||
if (errno != EINTR) {
|
peer->read.capacity = MAX(8192u, peer->read.capacity * 2);
|
||||||
perror("Error reading from talk peer");
|
peer->read.data = realloc(peer->read.data, peer->read.capacity);
|
||||||
failed("");
|
if (!peer->read.data) failed("Out of memory");
|
||||||
}
|
}
|
||||||
} else {
|
ssize_t n = recv(peer->fd, peer->read.data + peer->read.used, peer->read.capacity - peer->read.used, 0);
|
||||||
if (!rd->used && memcmp(rd->data, KITTY_CMD_PREFIX, sizeof(KITTY_CMD_PREFIX)-1) == 0) rd->is_peer_command = true;
|
if (n == 0) {
|
||||||
rd->used += n;
|
peer->read.finished = true;
|
||||||
while (rd->is_peer_command) dispatch_peer_command(self, rd, s);
|
shutdown(peer->fd, SHUT_RD);
|
||||||
}
|
while (has_complete_peer_command(peer)) dispatch_peer_command(self, peer);
|
||||||
break;
|
queue_peer_message(self, peer);
|
||||||
}
|
free(peer->read.data); peer->read.data = NULL;
|
||||||
|
peer->read.used = 0; peer->read.capacity = 0;
|
||||||
|
} else if (n < 0) {
|
||||||
|
if (errno != EINTR) failed(strerror(errno));
|
||||||
|
} else {
|
||||||
|
peer->read.used += n;
|
||||||
|
while (has_complete_peer_command(peer)) dispatch_peer_command(self, peer);
|
||||||
}
|
}
|
||||||
#undef failed
|
#undef failed
|
||||||
return read_finished;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline bool
|
|
||||||
write_to_peer(int fd) {
|
|
||||||
bool write_finished = false;
|
|
||||||
for (size_t i = 0; i < talk_data.num_writes; i++) {
|
|
||||||
PeerWriteData *wd = talk_data.writes + i;
|
|
||||||
#define failed(msg) { write_finished = true; log_error("%s", msg); wd->finished = true; break; }
|
|
||||||
if (wd->fd == fd) {
|
|
||||||
ssize_t n = send(fd, wd->data + wd->pos, wd->sz - wd->pos, MSG_NOSIGNAL);
|
|
||||||
if (n == 0) { failed("send() to peer failed to send any data"); }
|
|
||||||
else if (n < 0) {
|
|
||||||
if (errno != EINTR) { perror("write() to peer socket failed with error"); failed(""); }
|
|
||||||
} else {
|
|
||||||
wd->pos += n;
|
|
||||||
if (wd->pos >= wd->sz) { write_finished = true; wd->finished = true; }
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
#undef failed
|
|
||||||
return write_finished;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
remove_poll_fd(int fd) {
|
write_to_peer(Peer *peer) {
|
||||||
size_t count = talk_data.num_talk_fds + talk_data.num_listen_fds;
|
talk_mutex(lock);
|
||||||
for (size_t i = talk_data.num_listen_fds; i < count; i++) {
|
ssize_t n = send(peer->fd, peer->write.data, peer->write.used, MSG_NOSIGNAL);
|
||||||
struct pollfd *pfd = talk_data.fds + i;
|
if (n == 0) { log_error("send() to peer failed to send any data"); peer->write.used = 0; peer->write.failed = true; }
|
||||||
if (pfd->fd == fd) {
|
else if (n < 0) {
|
||||||
size_t num_to_right = count - 1 - i;
|
if (errno != EINTR) { log_error("write() to peer socket failed with error: %s", strerror(errno)); peer->write.used = 0; peer->write.failed = true; }
|
||||||
if (num_to_right) memmove(talk_data.fds + i, talk_data.fds + i + 1, num_to_right * sizeof(struct pollfd));
|
} else {
|
||||||
talk_data.num_talk_fds--;
|
if ((size_t)n > peer->write.used) memmove(peer->write.data, peer->write.data + n, peer->write.used - n);
|
||||||
break;
|
peer->write.used -= n;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
prune_finished_reads(void) {
|
|
||||||
if (!talk_data.num_reads) return;
|
|
||||||
for (ssize_t i = talk_data.num_reads - 1; i >= 0; i--) {
|
|
||||||
PeerReadData *rd = talk_data.reads + i;
|
|
||||||
if (rd->finished) {
|
|
||||||
remove_poll_fd(rd->fd);
|
|
||||||
if (rd->close_socket) { nuke_socket(rd->fd); }
|
|
||||||
else shutdown(rd->fd, SHUT_RD);
|
|
||||||
free(rd->data);
|
|
||||||
ssize_t num_to_right = talk_data.num_reads - 1 - i;
|
|
||||||
if (num_to_right > 0) memmove(talk_data.reads + i, talk_data.reads + i + 1, num_to_right * sizeof(PeerReadData));
|
|
||||||
else talk_data.reads[i] = empty_prd;
|
|
||||||
talk_data.num_reads--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
prune_finished_writes(void) {
|
|
||||||
if (!talk_data.num_writes) return;
|
|
||||||
for (ssize_t i = talk_data.num_writes - 1; i >= 0; i--) {
|
|
||||||
PeerWriteData *wd = talk_data.writes + i;
|
|
||||||
if (wd->finished) {
|
|
||||||
remove_poll_fd(wd->fd);
|
|
||||||
shutdown(wd->fd, SHUT_WR); safe_close(wd->fd);
|
|
||||||
free(wd->data);
|
|
||||||
ssize_t num_to_right = talk_data.num_writes - 1 - i;
|
|
||||||
if (num_to_right > 0) memmove(talk_data.writes + i, talk_data.writes + i + 1, num_to_right * sizeof(PeerWriteData));
|
|
||||||
else talk_data.writes[i] = empty_pwd;
|
|
||||||
talk_data.num_writes--;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
talk_mutex(unlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -1481,92 +1447,107 @@ wakeup_talk_loop(bool in_signal_handler) {
|
|||||||
if (talk_thread_started) wakeup_loop(&talk_data.loop_data, in_signal_handler, "talk_loop");
|
if (talk_thread_started) wakeup_loop(&talk_data.loop_data, in_signal_handler, "talk_loop");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
move_queued_writes(void) {
|
prune_peers(void) {
|
||||||
while (talk_data.num_queued_writes) {
|
for (size_t i = 0; i < talk_data.num_peers; i++) {
|
||||||
PeerWriteData *src = talk_data.queued_writes + --talk_data.num_queued_writes;
|
size_t idx = talk_data.num_peers - 1 - i;
|
||||||
size_t fd_idx = talk_data.num_listen_fds + talk_data.num_talk_fds;
|
Peer *p = talk_data.peers + idx;
|
||||||
if (fd_idx < PEER_LIMIT && talk_data.num_writes < PEER_LIMIT) {
|
if (p->read.finished && !p->num_of_unresponded_messages_sent_to_main_thread && !p->write.used) {
|
||||||
ensure_space_for(&talk_data, fds, PollFD, fd_idx + 1, fds_capacity, 8, false);
|
free_peer(p);
|
||||||
talk_data.fds[fd_idx].fd = src->fd; talk_data.fds[fd_idx].events = POLLOUT;
|
remove_i_from_array(talk_data.peers, idx, talk_data.num_peers);
|
||||||
ensure_space_for(&talk_data, writes, PeerWriteData, talk_data.num_writes + 1, writes_capacity, 8, false);
|
|
||||||
talk_data.writes[talk_data.num_writes++] = *src;
|
|
||||||
talk_data.num_talk_fds++;
|
|
||||||
} else {
|
|
||||||
log_error("Cannot send response to peer, too many peers");
|
|
||||||
free(src->data); nuke_socket(src->fd);
|
|
||||||
}
|
}
|
||||||
*src = empty_pwd;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void*
|
static void*
|
||||||
talk_loop(void *data) {
|
talk_loop(void *data) {
|
||||||
// The talk thread loop
|
// The talk thread loop
|
||||||
|
|
||||||
ChildMonitor *self = (ChildMonitor*)data;
|
ChildMonitor *self = (ChildMonitor*)data;
|
||||||
set_thread_name("KittyPeerMon");
|
set_thread_name("KittyPeerMon");
|
||||||
if ((pthread_mutex_init(&talk_data.peer_lock, NULL)) != 0) { perror("Failed to create peer mutex"); return 0; }
|
|
||||||
if (!init_loop_data(&talk_data.loop_data)) { log_error("Failed to create wakeup fd for talk thread with error: %s", strerror(errno)); }
|
if (!init_loop_data(&talk_data.loop_data)) { log_error("Failed to create wakeup fd for talk thread with error: %s", strerror(errno)); }
|
||||||
ensure_space_for(&talk_data, fds, PollFD, 8, fds_capacity, 8, false);
|
PollFD fds[PEER_LIMIT + 8] = {0};
|
||||||
|
size_t num_listen_fds = 0, num_peer_fds = 0;
|
||||||
#define add_listener(which) \
|
#define add_listener(which) \
|
||||||
if (self->which > -1) { \
|
if (self->which > -1) { \
|
||||||
talk_data.fds[talk_data.num_listen_fds].fd = self->which; talk_data.fds[talk_data.num_listen_fds++].events = POLLIN; \
|
fds[num_listen_fds].fd = self->which; fds[num_listen_fds++].events = POLLIN; \
|
||||||
}
|
}
|
||||||
add_listener(talk_fd); add_listener(listen_fd);
|
add_listener(talk_fd); add_listener(listen_fd);
|
||||||
#undef add_listener
|
#undef add_listener
|
||||||
talk_data.fds[talk_data.num_listen_fds].fd = talk_data.loop_data.wakeup_read_fd; talk_data.fds[talk_data.num_listen_fds++].events = POLLIN;
|
fds[num_listen_fds].fd = talk_data.loop_data.wakeup_read_fd; fds[num_listen_fds++].events = POLLIN;
|
||||||
|
|
||||||
while (LIKELY(!self->shutting_down)) {
|
while (LIKELY(!self->shutting_down)) {
|
||||||
for (size_t i = 0; i < talk_data.num_listen_fds + talk_data.num_talk_fds; i++) { talk_data.fds[i].revents = 0; }
|
num_peer_fds = 0;
|
||||||
int ret = poll(talk_data.fds, talk_data.num_listen_fds + talk_data.num_talk_fds, -1);
|
if (talk_data.num_peers > 0) {
|
||||||
|
talk_mutex(lock);
|
||||||
|
prune_peers();
|
||||||
|
for (size_t i = 0; i < talk_data.num_peers; i++) {
|
||||||
|
Peer *p = talk_data.peers + i;
|
||||||
|
if (!p->read.finished || p->write.used) {
|
||||||
|
fds[num_listen_fds + num_peer_fds].fd = p->fd;
|
||||||
|
fds[num_listen_fds + num_peer_fds].revents = 0;
|
||||||
|
int flags = 0;
|
||||||
|
if (!p->read.finished) flags |= POLLIN;
|
||||||
|
if (p->write.used) flags |= POLLOUT;
|
||||||
|
fds[num_listen_fds + num_peer_fds++].events = flags;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
talk_mutex(unlock);
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < num_listen_fds; i++) fds[i].revents = 0;
|
||||||
|
int ret = poll(fds, num_listen_fds + num_peer_fds, -1);
|
||||||
if (ret > 0) {
|
if (ret > 0) {
|
||||||
bool has_finished_reads = false, has_finished_writes = false;
|
for (size_t i = 0; i < num_listen_fds - 1; i++) {
|
||||||
for (size_t i = 0; i < talk_data.num_listen_fds - 1; i++) {
|
if (fds[i].revents & POLLIN) {
|
||||||
if (talk_data.fds[i].revents & POLLIN) {if (!accept_peer(talk_data.fds[i].fd, self->shutting_down)) goto end; }
|
if (!accept_peer(fds[i].fd, self->shutting_down)) goto end;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (talk_data.fds[talk_data.num_listen_fds - 1].revents & POLLIN) drain_fd(talk_data.fds[talk_data.num_listen_fds - 1].fd); // wakeup
|
if (fds[num_listen_fds - 1].revents & POLLIN) {
|
||||||
for (size_t i = talk_data.num_listen_fds; i < talk_data.num_talk_fds + talk_data.num_listen_fds; i++) {
|
drain_fd(fds[num_listen_fds - 1].fd); // wakeup
|
||||||
if (talk_data.fds[i].revents & (POLLIN | POLLHUP)) { if (read_from_peer(self, talk_data.fds[i].fd)) has_finished_reads = true; }
|
}
|
||||||
if (talk_data.fds[i].revents & POLLOUT) { if (write_to_peer(talk_data.fds[i].fd)) has_finished_writes = true; }
|
for (size_t i = num_listen_fds, k = 0; i < num_peer_fds + num_listen_fds; i++, k++) {
|
||||||
|
Peer *p = talk_data.peers + k;
|
||||||
|
if (fds[i].revents & (POLLIN | POLLHUP)) read_from_peer(self, p);
|
||||||
|
if (fds[i].revents & POLLOUT) write_to_peer(p);
|
||||||
|
if (fds[i].revents & POLLNVAL) {
|
||||||
|
p->read.finished = true;
|
||||||
|
p->write.failed = true; p->write.used = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (has_finished_reads) prune_finished_reads();
|
|
||||||
if (has_finished_writes) prune_finished_writes();
|
|
||||||
peer_mutex(lock);
|
|
||||||
if (talk_data.num_queued_writes) move_queued_writes();
|
|
||||||
peer_mutex(unlock);
|
|
||||||
} else if (ret < 0) { if (errno != EAGAIN && errno != EINTR) perror("poll() on talk fds failed"); }
|
} else if (ret < 0) { if (errno != EAGAIN && errno != EINTR) perror("poll() on talk fds failed"); }
|
||||||
}
|
}
|
||||||
end:
|
end:
|
||||||
free_loop_data(&talk_data.loop_data);
|
free_loop_data(&talk_data.loop_data);
|
||||||
free(talk_data.fds); free(talk_data.reads); free(talk_data.writes); free(talk_data.queued_writes);
|
for (size_t i = 0; i < talk_data.num_peers; i++) free_peer(talk_data.peers + i);
|
||||||
|
free(talk_data.peers);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
|
||||||
add_peer_writer(int fd, const char* msg, size_t msg_sz) {
|
|
||||||
bool ok = false;
|
|
||||||
peer_mutex(lock);
|
|
||||||
if (talk_data.num_queued_writes < PEER_LIMIT) {
|
|
||||||
ensure_space_for(&talk_data, queued_writes, PeerWriteData, talk_data.num_queued_writes + 1, queued_writes_capacity, 8, false);
|
|
||||||
talk_data.queued_writes[talk_data.num_queued_writes] = empty_pwd;
|
|
||||||
talk_data.queued_writes[talk_data.num_queued_writes].data = malloc(msg_sz);
|
|
||||||
if (talk_data.queued_writes[talk_data.num_queued_writes].data) {
|
|
||||||
memcpy(talk_data.queued_writes[talk_data.num_queued_writes].data, msg, msg_sz);
|
|
||||||
talk_data.queued_writes[talk_data.num_queued_writes].sz = msg_sz;
|
|
||||||
talk_data.queued_writes[talk_data.num_queued_writes++].fd = fd;
|
|
||||||
ok = true;
|
|
||||||
}
|
|
||||||
} else log_error("Cannot send response to peer, too many peers");
|
|
||||||
peer_mutex(unlock);
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
send_response(int fd, const char *msg, size_t msg_sz) {
|
send_response(id_type peer_id, const char *msg, size_t msg_sz) {
|
||||||
if (msg == NULL) { shutdown(fd, SHUT_WR); safe_close(fd); return; }
|
bool wakeup = false;
|
||||||
if (!add_peer_writer(fd, msg, msg_sz)) { shutdown(fd, SHUT_WR); safe_close(fd); }
|
talk_mutex(lock);
|
||||||
else wakeup_talk_loop(false);
|
for (size_t i = 0; i < talk_data.num_peers; i++) {
|
||||||
|
Peer *peer = talk_data.peers + i;
|
||||||
|
if (peer->id == peer_id) {
|
||||||
|
if (peer->num_of_unresponded_messages_sent_to_main_thread) peer->num_of_unresponded_messages_sent_to_main_thread--;
|
||||||
|
if (!peer->write.failed) {
|
||||||
|
if (peer->write.capacity - peer->write.used < msg_sz) {
|
||||||
|
void *data = realloc(peer->write.data, peer->write.capacity + msg_sz);
|
||||||
|
if (data) {
|
||||||
|
peer->write.data = data;
|
||||||
|
peer->write.capacity += msg_sz;
|
||||||
|
} else fatal("Out of memory");
|
||||||
|
}
|
||||||
|
if (msg) memcpy(peer->write.data + peer->write.used, msg, msg_sz);
|
||||||
|
peer->write.used += msg_sz;
|
||||||
|
}
|
||||||
|
wakeup = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
talk_mutex(unlock);
|
||||||
|
if (wakeup) wakeup_talk_loop(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// }}}
|
// }}}
|
||||||
|
|||||||
@@ -99,28 +99,28 @@ spawn(PyObject *self UNUSED, PyObject *args) {
|
|||||||
// On BSD open() does not establish the controlling terminal
|
// On BSD open() does not establish the controlling terminal
|
||||||
if (ioctl(tfd, TIOCSCTTY, 0) == -1) exit_on_err("Failed to set controlling terminal with TIOCSCTTY");
|
if (ioctl(tfd, TIOCSCTTY, 0) == -1) exit_on_err("Failed to set controlling terminal with TIOCSCTTY");
|
||||||
#endif
|
#endif
|
||||||
safe_close(tfd);
|
safe_close(tfd, __FILE__, __LINE__);
|
||||||
|
|
||||||
// Redirect stdin/stdout/stderr to the pty
|
// Redirect stdin/stdout/stderr to the pty
|
||||||
if (dup2(slave, 1) == -1) exit_on_err("dup2() failed for fd number 1");
|
if (dup2(slave, 1) == -1) exit_on_err("dup2() failed for fd number 1");
|
||||||
if (dup2(slave, 2) == -1) exit_on_err("dup2() failed for fd number 2");
|
if (dup2(slave, 2) == -1) exit_on_err("dup2() failed for fd number 2");
|
||||||
if (stdin_read_fd > -1) {
|
if (stdin_read_fd > -1) {
|
||||||
if (dup2(stdin_read_fd, 0) == -1) exit_on_err("dup2() failed for fd number 0");
|
if (dup2(stdin_read_fd, 0) == -1) exit_on_err("dup2() failed for fd number 0");
|
||||||
safe_close(stdin_read_fd);
|
safe_close(stdin_read_fd, __FILE__, __LINE__);
|
||||||
safe_close(stdin_write_fd);
|
safe_close(stdin_write_fd, __FILE__, __LINE__);
|
||||||
} else {
|
} else {
|
||||||
if (dup2(slave, 0) == -1) exit_on_err("dup2() failed for fd number 0");
|
if (dup2(slave, 0) == -1) exit_on_err("dup2() failed for fd number 0");
|
||||||
}
|
}
|
||||||
safe_close(slave);
|
safe_close(slave, __FILE__, __LINE__);
|
||||||
safe_close(master);
|
safe_close(master, __FILE__, __LINE__);
|
||||||
|
|
||||||
// Wait for READY_SIGNAL which indicates kitty has setup the screen object
|
// Wait for READY_SIGNAL which indicates kitty has setup the screen object
|
||||||
safe_close(ready_write_fd);
|
safe_close(ready_write_fd, __FILE__, __LINE__);
|
||||||
wait_for_terminal_ready(ready_read_fd);
|
wait_for_terminal_ready(ready_read_fd);
|
||||||
safe_close(ready_read_fd);
|
safe_close(ready_read_fd, __FILE__, __LINE__);
|
||||||
|
|
||||||
// Close any extra fds inherited from parent
|
// Close any extra fds inherited from parent
|
||||||
for (int c = 3; c < 201; c++) safe_close(c);
|
for (int c = 3; c < 201; c++) safe_close(c, __FILE__, __LINE__);
|
||||||
|
|
||||||
environ = env;
|
environ = env;
|
||||||
// for some reason SIGPIPE is set to SIG_IGN, so reset it, needed by bash,
|
// for some reason SIGPIPE is set to SIG_IGN, so reset it, needed by bash,
|
||||||
|
|||||||
@@ -210,6 +210,8 @@ class Child:
|
|||||||
env: Optional[Dict[str, str]] = getattr(self, '_final_env', None)
|
env: Optional[Dict[str, str]] = getattr(self, '_final_env', None)
|
||||||
if env is None:
|
if env is None:
|
||||||
env = self._final_env = default_env().copy()
|
env = self._final_env = default_env().copy()
|
||||||
|
if is_macos and env.get('LC_CTYPE') == 'UTF-8' and not sys._xoptions.get('lc_ctype_before_python'):
|
||||||
|
del env['LC_CTYPE']
|
||||||
env.update(self.env)
|
env.update(self.env)
|
||||||
env['TERM'] = self.opts.term
|
env['TERM'] = self.opts.term
|
||||||
env['COLORTERM'] = 'truecolor'
|
env['COLORTERM'] = 'truecolor'
|
||||||
|
|||||||
18
kitty/cli.py
18
kitty/cli.py
@@ -544,6 +544,15 @@ def parse_cmdline(oc: Options, disabled: OptionSpecSeq, ans: Any, args: Optional
|
|||||||
return leftover_args
|
return leftover_args
|
||||||
|
|
||||||
|
|
||||||
|
WATCHER_DEFINITION = '''
|
||||||
|
--watcher -w
|
||||||
|
type=list
|
||||||
|
Path to a python file. Appropriately named functions in this file will be called
|
||||||
|
for various events, such as when the window is resized, focused or closed. See the section
|
||||||
|
on watchers in the launch command documentation :doc:`launch`. Relative paths are
|
||||||
|
resolved relative to the kitty config directory.'''
|
||||||
|
|
||||||
|
|
||||||
def options_spec() -> str:
|
def options_spec() -> str:
|
||||||
if not hasattr(options_spec, 'ans'):
|
if not hasattr(options_spec, 'ans'):
|
||||||
OPTIONS = '''
|
OPTIONS = '''
|
||||||
@@ -593,6 +602,11 @@ Path to a file containing the startup :italic:`session` (tabs, windows, layout,
|
|||||||
Use - to read from STDIN. See the README file for details and an example.
|
Use - to read from STDIN. See the README file for details and an example.
|
||||||
|
|
||||||
|
|
||||||
|
{watcher}
|
||||||
|
Note that this watcher will be added only to all initially created windows, not new windows
|
||||||
|
created after startup.
|
||||||
|
|
||||||
|
|
||||||
--hold
|
--hold
|
||||||
type=bool-set
|
type=bool-set
|
||||||
Remain open after child process exits. Note that this only affects the first
|
Remain open after child process exits. Note that this only affects the first
|
||||||
@@ -695,8 +709,8 @@ type=bool-set
|
|||||||
!
|
!
|
||||||
'''
|
'''
|
||||||
setattr(options_spec, 'ans', OPTIONS.format(
|
setattr(options_spec, 'ans', OPTIONS.format(
|
||||||
appname=appname, config_help=CONFIG_HELP.format(appname=appname, conf_name=appname)
|
appname=appname, config_help=CONFIG_HELP.format(appname=appname, conf_name=appname),
|
||||||
|
watcher=WATCHER_DEFINITION
|
||||||
))
|
))
|
||||||
ans: str = getattr(options_spec, 'ans')
|
ans: str = getattr(options_spec, 'ans')
|
||||||
return ans
|
return ans
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class CLIOptions:
|
|||||||
|
|
||||||
LaunchCLIOptions = AskCLIOptions = ClipboardCLIOptions = DiffCLIOptions = CLIOptions
|
LaunchCLIOptions = AskCLIOptions = ClipboardCLIOptions = DiffCLIOptions = CLIOptions
|
||||||
HintsCLIOptions = IcatCLIOptions = PanelCLIOptions = ResizeCLIOptions = CLIOptions
|
HintsCLIOptions = IcatCLIOptions = PanelCLIOptions = ResizeCLIOptions = CLIOptions
|
||||||
ErrorCLIOptions = UnicodeCLIOptions = RCOptions = CLIOptions
|
ErrorCLIOptions = UnicodeCLIOptions = RCOptions = RemoteFileCLIOptions = CLIOptions
|
||||||
|
|
||||||
|
|
||||||
def generate_stub() -> None:
|
def generate_stub() -> None:
|
||||||
@@ -35,6 +35,9 @@ def generate_stub() -> None:
|
|||||||
from kittens.ask.main import option_text
|
from kittens.ask.main import option_text
|
||||||
do(option_text(), 'AskCLIOptions')
|
do(option_text(), 'AskCLIOptions')
|
||||||
|
|
||||||
|
from kittens.remote_file.main import option_text
|
||||||
|
do(option_text(), 'RemoteFileCLIOptions')
|
||||||
|
|
||||||
from kittens.clipboard.main import OPTIONS
|
from kittens.clipboard.main import OPTIONS
|
||||||
do(OPTIONS(), 'ClipboardCLIOptions')
|
do(OPTIONS(), 'ClipboardCLIOptions')
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,14 @@ def screen_bell() -> None:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def screen_normal_keypad_mode() -> None:
|
||||||
|
write('\x1b>')
|
||||||
|
|
||||||
|
|
||||||
|
def screen_alternate_keypad_mode() -> None:
|
||||||
|
write('\x1b=')
|
||||||
|
|
||||||
|
|
||||||
def screen_cursor_position(y: int, x: int) -> None:
|
def screen_cursor_position(y: int, x: int) -> None:
|
||||||
write(CSI + '%s;%sH' % (y, x))
|
write(CSI + '%s;%sH' % (y, x))
|
||||||
|
|
||||||
@@ -80,6 +88,18 @@ def screen_indexn(n: int) -> None:
|
|||||||
write(CSI + '%dS' % n)
|
write(CSI + '%dS' % n)
|
||||||
|
|
||||||
|
|
||||||
|
def screen_delete_characters(count: int) -> None:
|
||||||
|
write(CSI + '%dP' % count)
|
||||||
|
|
||||||
|
|
||||||
|
def screen_insert_characters(count: int) -> None:
|
||||||
|
write(CSI + '%d@' % count)
|
||||||
|
|
||||||
|
|
||||||
|
def screen_scroll(count: int) -> None:
|
||||||
|
write(CSI + '%dS' % count)
|
||||||
|
|
||||||
|
|
||||||
def screen_erase_in_display(how: int, private: bool) -> None:
|
def screen_erase_in_display(how: int, private: bool) -> None:
|
||||||
write(CSI + ('?' if private else '') + str(how) + 'J')
|
write(CSI + ('?' if private else '') + str(how) + 'J')
|
||||||
|
|
||||||
@@ -108,6 +128,10 @@ def screen_linefeed() -> None:
|
|||||||
write('\n')
|
write('\n')
|
||||||
|
|
||||||
|
|
||||||
|
def screen_tab() -> None:
|
||||||
|
write('\t')
|
||||||
|
|
||||||
|
|
||||||
def screen_backspace() -> None:
|
def screen_backspace() -> None:
|
||||||
write('\x08')
|
write('\x08')
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,9 @@
|
|||||||
#include "state.h"
|
#include "state.h"
|
||||||
#include "monotonic.h"
|
#include "monotonic.h"
|
||||||
#include <Cocoa/Cocoa.h>
|
#include <Cocoa/Cocoa.h>
|
||||||
|
#ifndef KITTY_USE_DEPRECATED_MACOS_NOTIFICATION_API
|
||||||
|
#include <UserNotifications/UserNotifications.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <AvailabilityMacros.h>
|
#include <AvailabilityMacros.h>
|
||||||
// Needed for _NSGetProgname
|
// Needed for _NSGetProgname
|
||||||
@@ -133,6 +136,7 @@ get_dock_menu(id self UNUSED, SEL _cmd UNUSED, NSApplication *sender UNUSED) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *notification_activated_callback = NULL;
|
static PyObject *notification_activated_callback = NULL;
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
set_notification_activated_callback(PyObject *self UNUSED, PyObject *callback) {
|
set_notification_activated_callback(PyObject *self UNUSED, PyObject *callback) {
|
||||||
if (notification_activated_callback) Py_DECREF(notification_activated_callback);
|
if (notification_activated_callback) Py_DECREF(notification_activated_callback);
|
||||||
@@ -141,6 +145,8 @@ set_notification_activated_callback(PyObject *self UNUSED, PyObject *callback) {
|
|||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef KITTY_USE_DEPRECATED_MACOS_NOTIFICATION_API
|
||||||
|
|
||||||
@interface NotificationDelegate : NSObject <NSUserNotificationCenterDelegate>
|
@interface NotificationDelegate : NSObject <NSUserNotificationCenterDelegate>
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -170,24 +176,12 @@ set_notification_activated_callback(PyObject *self UNUSED, PyObject *callback) {
|
|||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
|
cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
|
||||||
char *identifier = NULL, *title = NULL, *subtitle = NULL, *informativeText = NULL, *path_to_image = NULL;
|
char *identifier = NULL, *title = NULL, *informativeText = NULL, *subtitle = NULL;
|
||||||
if (!PyArg_ParseTuple(args, "zssz|z", &identifier, &title, &informativeText, &path_to_image, &subtitle)) return NULL;
|
if (!PyArg_ParseTuple(args, "zsz|z", &identifier, &title, &informativeText, &subtitle)) return NULL;
|
||||||
NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
|
NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
|
||||||
if (!center) {PyErr_SetString(PyExc_RuntimeError, "Failed to get the user notification center"); return NULL; }
|
if (!center) {PyErr_SetString(PyExc_RuntimeError, "Failed to get the user notification center"); return NULL; }
|
||||||
if (!center.delegate) center.delegate = [[NotificationDelegate alloc] init];
|
if (!center.delegate) center.delegate = [[NotificationDelegate alloc] init];
|
||||||
NSUserNotification *n = [NSUserNotification new];
|
NSUserNotification *n = [NSUserNotification new];
|
||||||
NSImage *img = nil;
|
|
||||||
if (path_to_image) {
|
|
||||||
NSString *p = @(path_to_image);
|
|
||||||
NSURL *url = [NSURL fileURLWithPath:p];
|
|
||||||
img = [[NSImage alloc] initWithContentsOfURL:url];
|
|
||||||
[url release]; [p release];
|
|
||||||
if (img) {
|
|
||||||
[n setValue:img forKey:@"_identityImage"];
|
|
||||||
[n setValue:@(false) forKey:@"_identityImageHasBorder"];
|
|
||||||
}
|
|
||||||
[img release];
|
|
||||||
}
|
|
||||||
#define SET(x) { \
|
#define SET(x) { \
|
||||||
if (x) { \
|
if (x) { \
|
||||||
NSString *t = @(x); \
|
NSString *t = @(x); \
|
||||||
@@ -203,6 +197,111 @@ cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
|
|||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
@interface NotificationDelegate : NSObject <UNUserNotificationCenterDelegate>
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation NotificationDelegate
|
||||||
|
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
|
||||||
|
didReceiveNotificationResponse:(UNNotificationResponse *)response
|
||||||
|
withCompletionHandler:(void (^)(void))completionHandler {
|
||||||
|
(void)(center);
|
||||||
|
if (notification_activated_callback) {
|
||||||
|
NSString *identifier = [[[response notification] request] identifier];
|
||||||
|
PyObject *ret = PyObject_CallFunction(notification_activated_callback, "z",
|
||||||
|
identifier ? [identifier UTF8String] : NULL);
|
||||||
|
if (ret == NULL) PyErr_Print();
|
||||||
|
else Py_DECREF(ret);
|
||||||
|
}
|
||||||
|
completionHandler();
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
schedule_notification(const char *identifier, const char *title, const char *body, const char *subtitle) {
|
||||||
|
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
|
||||||
|
if (!center) return;
|
||||||
|
// Configure the notification's payload.
|
||||||
|
UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
|
||||||
|
if (title) content.title = @(title);
|
||||||
|
if (body) content.body = @(body);
|
||||||
|
if (subtitle) content.subtitle = @(subtitle);
|
||||||
|
// Deliver the notification
|
||||||
|
static unsigned long counter = 1;
|
||||||
|
UNNotificationRequest* request = [
|
||||||
|
UNNotificationRequest requestWithIdentifier:(identifier ? @(identifier) : [NSString stringWithFormat:@"Id_%lu", counter++])
|
||||||
|
content:content trigger:nil];
|
||||||
|
[center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
|
||||||
|
if (error != nil) {
|
||||||
|
log_error("Failed to show notification: %s", [[error localizedDescription] UTF8String]);
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
[content release];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *identifier, *title, *body, *subtitle;
|
||||||
|
} QueuedNotification;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
QueuedNotification *notifications;
|
||||||
|
size_t count, capacity;
|
||||||
|
} NotificationQueue;
|
||||||
|
static NotificationQueue notification_queue = {0};
|
||||||
|
|
||||||
|
static void
|
||||||
|
queue_notification(const char *identifier, const char *title, const char* body, const char* subtitle) {
|
||||||
|
ensure_space_for((¬ification_queue), notifications, QueuedNotification, notification_queue.count + 16, capacity, 16, true);
|
||||||
|
QueuedNotification *n = notification_queue.notifications + notification_queue.count++;
|
||||||
|
n->identifier = identifier ? strdup(identifier) : NULL;
|
||||||
|
n->title = title ? strdup(title) : NULL;
|
||||||
|
n->body = body ? strdup(body) : NULL;
|
||||||
|
n->subtitle = subtitle ? strdup(subtitle) : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
drain_pending_notifications(BOOL granted) {
|
||||||
|
if (granted) {
|
||||||
|
for (size_t i = 0; i < notification_queue.count; i++) {
|
||||||
|
QueuedNotification *n = notification_queue.notifications + i;
|
||||||
|
schedule_notification(n->identifier, n->title, n->body, n->subtitle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while(notification_queue.count) {
|
||||||
|
QueuedNotification *n = notification_queue.notifications + --notification_queue.count;
|
||||||
|
free(n->identifier); free(n->title); free(n->body); free(n->subtitle);
|
||||||
|
n->identifier = NULL; n->title = NULL; n->body = NULL; n->subtitle = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
|
||||||
|
char *identifier = NULL, *title = NULL, *body = NULL, *subtitle = NULL;
|
||||||
|
if (!PyArg_ParseTuple(args, "zsz|z", &identifier, &title, &body, &subtitle)) return NULL;
|
||||||
|
|
||||||
|
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
|
||||||
|
if (!center) Py_RETURN_NONE;
|
||||||
|
if (!center.delegate) center.delegate = [[NotificationDelegate alloc] init];
|
||||||
|
queue_notification(identifier, title, body, subtitle);
|
||||||
|
|
||||||
|
[center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert)
|
||||||
|
completionHandler:^(BOOL granted, NSError * _Nullable error) {
|
||||||
|
if (error != nil) {
|
||||||
|
log_error("Failed to request permission for showing notification: %s", [[error localizedDescription] UTF8String]);
|
||||||
|
}
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
drain_pending_notifications(granted);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
];
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
@interface ServiceProvider : NSObject
|
@interface ServiceProvider : NSObject
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -390,6 +489,12 @@ cocoa_focus_window(void *w) {
|
|||||||
[window makeKeyWindow];
|
[window makeKeyWindow];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long
|
||||||
|
cocoa_window_number(void *w) {
|
||||||
|
NSWindow *window = (NSWindow*)w;
|
||||||
|
return [window windowNumber];
|
||||||
|
}
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
cocoa_get_workspace_ids(void *w, size_t *workspace_ids, size_t array_sz) {
|
cocoa_get_workspace_ids(void *w, size_t *workspace_ids, size_t array_sz) {
|
||||||
NSWindow *window = (NSWindow*)w;
|
NSWindow *window = (NSWindow*)w;
|
||||||
@@ -491,8 +596,15 @@ cleanup() {
|
|||||||
|
|
||||||
if (dockMenu) [dockMenu release];
|
if (dockMenu) [dockMenu release];
|
||||||
dockMenu = nil;
|
dockMenu = nil;
|
||||||
if (notification_activated_callback) Py_DECREF(notification_activated_callback);
|
Py_CLEAR(notification_activated_callback);
|
||||||
notification_activated_callback = NULL;
|
|
||||||
|
#ifndef KITTY_USE_DEPRECATED_MACOS_NOTIFICATION_API
|
||||||
|
drain_pending_notifications(NO);
|
||||||
|
free(notification_queue.notifications);
|
||||||
|
notification_queue.notifications = NULL;
|
||||||
|
notification_queue.capacity = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
} // autoreleasepool
|
} // autoreleasepool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,6 +643,11 @@ cocoa_hide_titlebar(void *w)
|
|||||||
} // autoreleasepool
|
} // autoreleasepool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
cocoa_system_beep(void) {
|
||||||
|
NSBeep();
|
||||||
|
}
|
||||||
|
|
||||||
static PyMethodDef module_methods[] = {
|
static PyMethodDef module_methods[] = {
|
||||||
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
|
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
|
||||||
{"cocoa_set_new_window_trigger", (PyCFunction)cocoa_set_new_window_trigger, METH_VARARGS, ""},
|
{"cocoa_set_new_window_trigger", (PyCFunction)cocoa_set_new_window_trigger, METH_VARARGS, ""},
|
||||||
|
|||||||
@@ -254,6 +254,8 @@ def complete_kitty_cli_arg(ans: Completions, opt: Optional[OptionDict], prefix:
|
|||||||
complete_files_and_dirs(ans, prefix, files_group_name='Config files', predicate=is_conf_file)
|
complete_files_and_dirs(ans, prefix, files_group_name='Config files', predicate=is_conf_file)
|
||||||
elif dest == 'session':
|
elif dest == 'session':
|
||||||
complete_files_and_dirs(ans, prefix, files_group_name='Session files')
|
complete_files_and_dirs(ans, prefix, files_group_name='Session files')
|
||||||
|
elif dest == 'watcher':
|
||||||
|
complete_files_and_dirs(ans, prefix, files_group_name='Watcher files')
|
||||||
elif dest == 'directory':
|
elif dest == 'directory':
|
||||||
complete_files_and_dirs(ans, prefix, files_group_name='Directories', predicate=os.path.isdir)
|
complete_files_and_dirs(ans, prefix, files_group_name='Directories', predicate=os.path.isdir)
|
||||||
elif dest == 'start_as':
|
elif dest == 'start_as':
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from typing import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from ..rgb import Color, to_color as as_color
|
from ..rgb import Color, to_color as as_color
|
||||||
from ..utils import log_error
|
from ..utils import log_error, expandvars
|
||||||
|
|
||||||
key_pat = re.compile(r'([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$')
|
key_pat = re.compile(r'([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$')
|
||||||
T = TypeVar('T')
|
T = TypeVar('T')
|
||||||
@@ -53,13 +53,36 @@ def to_bool(x: str) -> bool:
|
|||||||
return x.lower() in ('y', 'yes', 'true')
|
return x.lower() in ('y', 'yes', 'true')
|
||||||
|
|
||||||
|
|
||||||
def to_cmdline(x: str) -> List[str]:
|
class ToCmdline:
|
||||||
return list(
|
|
||||||
map(
|
def __init__(self) -> None:
|
||||||
lambda y: os.path.expandvars(os.path.expanduser(y)),
|
self.override_env: Optional[Dict[str, str]] = None
|
||||||
shlex.split(x)
|
|
||||||
|
def __enter__(self) -> 'ToCmdline':
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, *a: Any) -> None:
|
||||||
|
self.override_env = None
|
||||||
|
|
||||||
|
def filter_env_vars(self, *a: str) -> 'ToCmdline':
|
||||||
|
remove = frozenset(a)
|
||||||
|
self.override_env = {k: v for k, v in os.environ.items() if k not in remove}
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __call__(self, x: str) -> List[str]:
|
||||||
|
return list(
|
||||||
|
map(
|
||||||
|
lambda y: expandvars(
|
||||||
|
os.path.expanduser(y),
|
||||||
|
os.environ if self.override_env is None else self.override_env,
|
||||||
|
fallback_to_os_env=False
|
||||||
|
),
|
||||||
|
shlex.split(x)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
|
to_cmdline = ToCmdline()
|
||||||
|
|
||||||
|
|
||||||
def python_string(text: str) -> str:
|
def python_string(text: str) -> str:
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ from .config_data import all_options, parse_mods, type_convert
|
|||||||
from .constants import cache_dir, defconf, is_macos
|
from .constants import cache_dir, defconf, is_macos
|
||||||
from .key_names import get_key_name_lookup, key_name_aliases
|
from .key_names import get_key_name_lookup, key_name_aliases
|
||||||
from .options_stub import Options as OptionsStub
|
from .options_stub import Options as OptionsStub
|
||||||
from .typing import EdgeLiteral, TypedDict
|
from .typing import TypedDict
|
||||||
from .utils import expandvars, log_error
|
from .utils import expandvars, log_error
|
||||||
|
|
||||||
KeySpec = Tuple[int, bool, int]
|
KeySpec = Tuple[int, bool, int]
|
||||||
@@ -393,7 +393,7 @@ class KeyDefinition:
|
|||||||
if len(expanded) > 1:
|
if len(expanded) > 1:
|
||||||
rest = expanded[1] + ' ' + rest
|
rest = expanded[1] + ' ' + rest
|
||||||
if changed:
|
if changed:
|
||||||
self.action = self.action._replace(args=[kitten + (' ' + rest).rstrip()])
|
self.action = self.action._replace(args=[kitten, rest.rstrip()])
|
||||||
|
|
||||||
|
|
||||||
def parse_key(val: str, key_definitions: List[KeyDefinition]) -> None:
|
def parse_key(val: str, key_definitions: List[KeyDefinition]) -> None:
|
||||||
@@ -718,51 +718,6 @@ def cached_values_for(name: str) -> Generator[Dict, None, None]:
|
|||||||
err))
|
err))
|
||||||
|
|
||||||
|
|
||||||
def initial_window_size_func(opts: OptionsStub, cached_values: Dict) -> Callable[[int, int, float, float, float, float], Tuple[int, int]]:
|
|
||||||
|
|
||||||
if 'window-size' in cached_values and opts.remember_window_size:
|
|
||||||
ws = cached_values['window-size']
|
|
||||||
try:
|
|
||||||
w, h = map(int, ws)
|
|
||||||
|
|
||||||
def initial_window_size(*a: Any) -> Tuple[int, int]:
|
|
||||||
return w, h
|
|
||||||
return initial_window_size
|
|
||||||
except Exception:
|
|
||||||
log_error('Invalid cached window size, ignoring')
|
|
||||||
|
|
||||||
w, w_unit = opts.initial_window_width
|
|
||||||
h, h_unit = opts.initial_window_height
|
|
||||||
|
|
||||||
def get_window_size(cell_width: int, cell_height: int, dpi_x: float, dpi_y: float, xscale: float, yscale: float) -> Tuple[int, int]:
|
|
||||||
if not is_macos:
|
|
||||||
# scaling is not needed on Wayland, but is needed on macOS. Not
|
|
||||||
# sure about X11.
|
|
||||||
xscale = yscale = 1
|
|
||||||
|
|
||||||
def effective_margin(which: EdgeLiteral) -> float:
|
|
||||||
ans: float = getattr(opts.single_window_margin_width, which)
|
|
||||||
if ans < 0:
|
|
||||||
ans = getattr(opts.window_margin_width, which)
|
|
||||||
return ans
|
|
||||||
|
|
||||||
if w_unit == 'cells':
|
|
||||||
spacing = effective_margin('left') + effective_margin('right')
|
|
||||||
spacing += opts.window_padding_width.left + opts.window_padding_width.right
|
|
||||||
width = cell_width * w / xscale + (dpi_x / 72) * spacing + 1
|
|
||||||
else:
|
|
||||||
width = w
|
|
||||||
if h_unit == 'cells':
|
|
||||||
spacing = effective_margin('top') + effective_margin('bottom')
|
|
||||||
spacing += opts.window_padding_width.top + opts.window_padding_width.bottom
|
|
||||||
height = cell_height * h / yscale + (dpi_y / 72) * spacing + 1
|
|
||||||
else:
|
|
||||||
height = h
|
|
||||||
return int(width), int(height)
|
|
||||||
|
|
||||||
return get_window_size
|
|
||||||
|
|
||||||
|
|
||||||
def commented_out_default_config() -> str:
|
def commented_out_default_config() -> str:
|
||||||
ans = []
|
ans = []
|
||||||
for line in as_conf_file(all_options.values()):
|
for line in as_conf_file(all_options.values()):
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from typing import (
|
|||||||
from . import fast_data_types as defines
|
from . import fast_data_types as defines
|
||||||
from .conf.definition import Option, Shortcut, option_func
|
from .conf.definition import Option, Shortcut, option_func
|
||||||
from .conf.utils import (
|
from .conf.utils import (
|
||||||
choices, positive_float, positive_int, to_bool, to_cmdline, to_color,
|
choices, positive_float, positive_int, to_bool, to_cmdline as tc, to_color,
|
||||||
to_color_or_none, unit_float
|
to_color_or_none, unit_float
|
||||||
)
|
)
|
||||||
from .constants import FloatEdges, config_dir, is_macos
|
from .constants import FloatEdges, config_dir, is_macos
|
||||||
@@ -29,6 +29,10 @@ mod_map = {'CTRL': 'CONTROL', 'CMD': 'SUPER', '⌘': 'SUPER',
|
|||||||
'⌥': 'ALT', 'OPTION': 'ALT', 'KITTY_MOD': 'KITTY'}
|
'⌥': 'ALT', 'OPTION': 'ALT', 'KITTY_MOD': 'KITTY'}
|
||||||
|
|
||||||
|
|
||||||
|
def to_cmdline(x: str) -> List[str]:
|
||||||
|
return tc(x)
|
||||||
|
|
||||||
|
|
||||||
def parse_mods(parts: Iterable[str], sc: str) -> Optional[int]:
|
def parse_mods(parts: Iterable[str], sc: str) -> Optional[int]:
|
||||||
|
|
||||||
def map_mod(m: str) -> str:
|
def map_mod(m: str) -> str:
|
||||||
@@ -321,7 +325,9 @@ Note that this code is indexed by PostScript name, and not the font
|
|||||||
family. This allows you to define very precise feature settings; e.g. you can
|
family. This allows you to define very precise feature settings; e.g. you can
|
||||||
disable a feature in the italic font but not in the regular font.
|
disable a feature in the italic font but not in the regular font.
|
||||||
|
|
||||||
To get the PostScript name for a font, use :code:`kitty + list-fonts --psnames`::
|
To get the PostScript name for a font, use :code:`kitty + list-fonts --psnames`:
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
$ kitty + list-fonts --psnames | grep Fira
|
$ kitty + list-fonts --psnames | grep Fira
|
||||||
Fira Code
|
Fira Code
|
||||||
@@ -440,8 +446,8 @@ def scrollback_pager_history_size(x: str) -> int:
|
|||||||
o('scrollback_lines', 2000, option_type=scrollback_lines, long_text=_('''
|
o('scrollback_lines', 2000, option_type=scrollback_lines, long_text=_('''
|
||||||
Number of lines of history to keep in memory for scrolling back. Memory is allocated
|
Number of lines of history to keep in memory for scrolling back. Memory is allocated
|
||||||
on demand. Negative numbers are (effectively) infinite scrollback. Note that using
|
on demand. Negative numbers are (effectively) infinite scrollback. Note that using
|
||||||
very large scrollback is not recommended as it can slow down resizing of the terminal
|
very large scrollback is not recommended as it can slow down performance of the terminal
|
||||||
and also use large amounts of RAM.'''))
|
and also use large amounts of RAM. Instead, consider using :opt:`scrollback_pager_history_size`.'''))
|
||||||
|
|
||||||
o('scrollback_pager', 'less --chop-long-lines --RAW-CONTROL-CHARS +INPUT_LINE_NUMBER', option_type=to_cmdline, long_text=_('''
|
o('scrollback_pager', 'less --chop-long-lines --RAW-CONTROL-CHARS +INPUT_LINE_NUMBER', option_type=to_cmdline, long_text=_('''
|
||||||
Program with which to view scrollback in a new window. The scrollback buffer is
|
Program with which to view scrollback in a new window. The scrollback buffer is
|
||||||
@@ -455,9 +461,9 @@ o('scrollback_pager_history_size', 0, option_type=scrollback_pager_history_size,
|
|||||||
Separate scrollback history size, used only for browsing the scrollback buffer (in MB).
|
Separate scrollback history size, used only for browsing the scrollback buffer (in MB).
|
||||||
This separate buffer is not available for interactive scrolling but will be
|
This separate buffer is not available for interactive scrolling but will be
|
||||||
piped to the pager program when viewing scrollback buffer in a separate window.
|
piped to the pager program when viewing scrollback buffer in a separate window.
|
||||||
The current implementation stores one character in 4 bytes, so approximatively
|
The current implementation stores the data in UTF-8, so approximatively
|
||||||
2500 lines per megabyte at 100 chars per line. A value of zero or less disables
|
10000 lines per megabyte at 100 chars per line, for pure ASCII text, unformatted text.
|
||||||
this feature. The maximum allowed size is 4GB.'''))
|
A value of zero or less disables this feature. The maximum allowed size is 4GB.'''))
|
||||||
|
|
||||||
o('wheel_scroll_multiplier', 5.0, long_text=_('''
|
o('wheel_scroll_multiplier', 5.0, long_text=_('''
|
||||||
Modify the amount scrolled by the mouse wheel. Note this is only used for low
|
Modify the amount scrolled by the mouse wheel. Note this is only used for low
|
||||||
@@ -668,9 +674,27 @@ for vertical resizing.
|
|||||||
'''))
|
'''))
|
||||||
o('window_resize_step_lines', 2, option_type=positive_int)
|
o('window_resize_step_lines', 2, option_type=positive_int)
|
||||||
|
|
||||||
o('window_border_width', 1.0, option_type=positive_float, long_text=_('''
|
|
||||||
The width (in pts) of window borders. Will be rounded to the nearest number of pixels based on screen resolution.
|
def window_border_width(x: Union[str, int, float]) -> Tuple[float, str]:
|
||||||
Note that borders are displayed only when more than one window is visible. They are meant to separate multiple windows.'''))
|
unit = 'pt'
|
||||||
|
if isinstance(x, str):
|
||||||
|
trailer = x[-2:]
|
||||||
|
if trailer in ('px', 'pt'):
|
||||||
|
unit = trailer
|
||||||
|
val = float(x[:-2])
|
||||||
|
else:
|
||||||
|
val = float(x)
|
||||||
|
else:
|
||||||
|
val = float(x)
|
||||||
|
return max(0, val), unit
|
||||||
|
|
||||||
|
|
||||||
|
o('window_border_width', '0.5pt', option_type=window_border_width, long_text=_('''
|
||||||
|
The width of window borders. Can be either in pixels (px) or pts (pt). Values
|
||||||
|
in pts will be rounded to the nearest number of pixels based on screen
|
||||||
|
resolution. If not specified the unit is assumed to be pts.
|
||||||
|
Note that borders are displayed only when more than one window
|
||||||
|
is visible. They are meant to separate multiple windows.'''))
|
||||||
|
|
||||||
o('draw_minimal_borders', True, long_text=_('''
|
o('draw_minimal_borders', True, long_text=_('''
|
||||||
Draw only the minimum borders needed. This means that only the minimum
|
Draw only the minimum borders needed. This means that only the minimum
|
||||||
@@ -783,10 +807,10 @@ Note that this does not currently work on Wayland.
|
|||||||
'''))
|
'''))
|
||||||
|
|
||||||
o('confirm_os_window_close', 0, option_type=positive_int, long_text=_('''
|
o('confirm_os_window_close', 0, option_type=positive_int, long_text=_('''
|
||||||
Ask for confirmation when closing an OS window that has at least this
|
Ask for confirmation when closing an OS window or a tab that has at least this
|
||||||
number of kitty windows in it. A value of zero disables confirmation.
|
number of kitty windows in it. A value of zero disables confirmation.
|
||||||
This confirmation also applies to requests to quit the entire application (all
|
This confirmation also applies to requests to quit the entire application (all
|
||||||
OS windows, via the quite action).
|
OS windows, via the quit action).
|
||||||
'''))
|
'''))
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
@@ -863,6 +887,17 @@ o('tab_separator', '"{}"'.format(default_tab_separator), option_type=tab_separat
|
|||||||
The separator between tabs in the tab bar when using :code:`separator` as the :opt:`tab_bar_style`.'''))
|
The separator between tabs in the tab bar when using :code:`separator` as the :opt:`tab_bar_style`.'''))
|
||||||
|
|
||||||
|
|
||||||
|
def tab_activity_symbol(x: str) -> Optional[str]:
|
||||||
|
if x == 'none':
|
||||||
|
return None
|
||||||
|
return x or None
|
||||||
|
|
||||||
|
|
||||||
|
o('tab_activity_symbol', 'none', option_type=tab_activity_symbol, long_text=_('''
|
||||||
|
Some text or a unicode symbol to show on the tab if a window in the tab that does
|
||||||
|
not have focus has some activity.'''))
|
||||||
|
|
||||||
|
|
||||||
def tab_title_template(x: str) -> str:
|
def tab_title_template(x: str) -> str:
|
||||||
if x:
|
if x:
|
||||||
for q in '\'"':
|
for q in '\'"':
|
||||||
@@ -1113,6 +1148,20 @@ that enabling the read functionality is a security risk as it means that any
|
|||||||
program, even one running on a remote server via SSH can read your clipboard.
|
program, even one running on a remote server via SSH can read your clipboard.
|
||||||
'''))
|
'''))
|
||||||
|
|
||||||
|
|
||||||
|
def allow_hyperlinks(x: str) -> int:
|
||||||
|
if x == 'ask':
|
||||||
|
return 0b11
|
||||||
|
return 1 if to_bool(x) else 0
|
||||||
|
|
||||||
|
|
||||||
|
o('allow_hyperlinks', 'yes', option_type=allow_hyperlinks, long_text=_('''
|
||||||
|
Process hyperlink (OSC 8) escape sequences. If disabled OSC 8 escape
|
||||||
|
sequences are ignored. Otherwise they become clickable links, that you
|
||||||
|
can click by holding down ctrl+shift and clicking with the mouse. The special
|
||||||
|
value of ``ask`` means that kitty will ask before opening the link.'''))
|
||||||
|
|
||||||
|
|
||||||
o('term', 'xterm-kitty', long_text=_('''
|
o('term', 'xterm-kitty', long_text=_('''
|
||||||
The value of the TERM environment variable to set. Changing this can break many
|
The value of the TERM environment variable to set. Changing this can break many
|
||||||
terminal programs, only change it if you know what you are doing, not because
|
terminal programs, only change it if you know what you are doing, not because
|
||||||
@@ -1402,6 +1451,7 @@ g('shortcuts.fonts') # {{{
|
|||||||
k('increase_font_size', 'kitty_mod+equal', 'change_font_size all +2.0', _('Increase font size'))
|
k('increase_font_size', 'kitty_mod+equal', 'change_font_size all +2.0', _('Increase font size'))
|
||||||
if is_macos:
|
if is_macos:
|
||||||
k('increase_font_size', 'cmd+plus', 'change_font_size all +2.0', _('Increase font size'), add_to_docs=False)
|
k('increase_font_size', 'cmd+plus', 'change_font_size all +2.0', _('Increase font size'), add_to_docs=False)
|
||||||
|
k('increase_font_size', 'cmd+shift+equal', 'change_font_size all +2.0', _('Increase font size'), add_to_docs=False)
|
||||||
k('decrease_font_size', 'kitty_mod+minus', 'change_font_size all -2.0', _('Decrease font size'))
|
k('decrease_font_size', 'kitty_mod+minus', 'change_font_size all -2.0', _('Decrease font size'))
|
||||||
if is_macos:
|
if is_macos:
|
||||||
k('decrease_font_size', 'cmd+minus', 'change_font_size all -2.0', _('Decrease font size'), add_to_docs=False)
|
k('decrease_font_size', 'cmd+minus', 'change_font_size all -2.0', _('Decrease font size'), add_to_docs=False)
|
||||||
@@ -1437,6 +1487,9 @@ k('goto_file_line', 'kitty_mod+p>n', 'kitten hints --type linenum', _('Open the
|
|||||||
Select something that looks like :code:`filename:linenum` and open it in vim at
|
Select something that looks like :code:`filename:linenum` and open it in vim at
|
||||||
the specified line number.'''))
|
the specified line number.'''))
|
||||||
|
|
||||||
|
k('open_selected_hyperlink', 'kitty_mod+p>y', 'kitten hints --type hyperlink', _('Open the selected hyperlink'), long_text=_('''
|
||||||
|
Select a hyperlink (i.e. a URL that has been marked as such by the terminal program, for example, by ls --hyperlink=auto).
|
||||||
|
'''))
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class Version(NamedTuple):
|
|||||||
|
|
||||||
|
|
||||||
appname: str = 'kitty'
|
appname: str = 'kitty'
|
||||||
version: Version = Version(0, 18, 1)
|
version: Version = Version(0, 19, 0)
|
||||||
str_version: str = '.'.join(map(str, version))
|
str_version: str = '.'.join(map(str, version))
|
||||||
_plat = sys.platform.lower()
|
_plat = sys.platform.lower()
|
||||||
is_macos: bool = 'darwin' in _plat
|
is_macos: bool = 'darwin' in _plat
|
||||||
@@ -64,16 +64,16 @@ class WindowGeometry(NamedTuple):
|
|||||||
def kitty_exe() -> str:
|
def kitty_exe() -> str:
|
||||||
rpath = sys._xoptions.get('bundle_exe_dir')
|
rpath = sys._xoptions.get('bundle_exe_dir')
|
||||||
if not rpath:
|
if not rpath:
|
||||||
items = filter(None, os.environ.get('PATH', '').split(os.pathsep))
|
items = os.environ.get('PATH', '').split(os.pathsep) + [os.path.join(base, 'launcher')]
|
||||||
seen: Set[str] = set()
|
seen: Set[str] = set()
|
||||||
for candidate in items:
|
for candidate in filter(None, items):
|
||||||
if candidate not in seen:
|
if candidate not in seen:
|
||||||
seen.add(candidate)
|
seen.add(candidate)
|
||||||
if os.access(os.path.join(candidate, 'kitty'), os.X_OK):
|
if os.access(os.path.join(candidate, 'kitty'), os.X_OK):
|
||||||
rpath = candidate
|
rpath = candidate
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
rpath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'launcher')
|
raise RuntimeError('kitty binary not found')
|
||||||
return os.path.join(rpath, 'kitty')
|
return os.path.join(rpath, 'kitty')
|
||||||
|
|
||||||
|
|
||||||
@@ -146,7 +146,7 @@ def wakeup() -> None:
|
|||||||
b.child_monitor.wakeup()
|
b.child_monitor.wakeup()
|
||||||
|
|
||||||
|
|
||||||
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
base_dir = os.path.dirname(base)
|
||||||
terminfo_dir = os.path.join(base_dir, 'terminfo')
|
terminfo_dir = os.path.join(base_dir, 'terminfo')
|
||||||
logo_data_file = os.path.join(base_dir, 'logo', 'kitty.rgba')
|
logo_data_file = os.path.join(base_dir, 'logo', 'kitty.rgba')
|
||||||
logo_png_file = os.path.join(base_dir, 'logo', 'kitty.png')
|
logo_png_file = os.path.join(base_dir, 'logo', 'kitty.png')
|
||||||
|
|||||||
@@ -110,10 +110,10 @@
|
|||||||
#define ESC_DECRC '8'
|
#define ESC_DECRC '8'
|
||||||
|
|
||||||
// Set normal keypad mode
|
// Set normal keypad mode
|
||||||
#define ESC_DECPNM '>'
|
#define ESC_DECKPNM '>'
|
||||||
|
|
||||||
// Set alternate keypad mode
|
// Set alternate keypad mode
|
||||||
#define ESC_DECPAM '='
|
#define ESC_DECKPAM '='
|
||||||
|
|
||||||
// ECMA-48 CSI sequences.
|
// ECMA-48 CSI sequences.
|
||||||
// ---------------------
|
// ---------------------
|
||||||
|
|||||||
@@ -409,7 +409,7 @@ render_color_glyph(CTFontRef font, uint8_t *buf, int glyph_id, unsigned int widt
|
|||||||
CGContextSetTextDrawingMode(ctx, kCGTextFill);
|
CGContextSetTextDrawingMode(ctx, kCGTextFill);
|
||||||
CGGlyph glyph = glyph_id;
|
CGGlyph glyph = glyph_id;
|
||||||
CGContextSetTextMatrix(ctx, transform);
|
CGContextSetTextMatrix(ctx, transform);
|
||||||
CGContextSetTextPosition(ctx, -boxes[0].origin.x, MAX(2, height - 1.2f * baseline)); // lower the emoji a bit so its bottom is not on the baseline
|
CGContextSetTextPosition(ctx, -boxes[0].origin.x, MAX(2, height - baseline));
|
||||||
CGPoint p = CGPointMake(0, 0);
|
CGPoint p = CGPointMake(0, 0);
|
||||||
CTFontDrawGlyphs(font, &glyph, &p, 1, ctx);
|
CTFontDrawGlyphs(font, &glyph, &p, 1, ctx);
|
||||||
CGContextRelease(ctx);
|
CGContextRelease(ctx);
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
#endif
|
#endif
|
||||||
#include "data-types.h"
|
#include "data-types.h"
|
||||||
#include "control-codes.h"
|
#include "control-codes.h"
|
||||||
|
#include "wcwidth-std.h"
|
||||||
|
#include "wcswidth.h"
|
||||||
#include "modes.h"
|
#include "modes.h"
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <termios.h>
|
#include <termios.h>
|
||||||
@@ -142,13 +144,21 @@ close_tty(PyObject *self UNUSED, PyObject *args) {
|
|||||||
TTY_ARGS
|
TTY_ARGS
|
||||||
tcsetattr(fd, TCSAFLUSH, termios_p); // deliberately ignore failure
|
tcsetattr(fd, TCSAFLUSH, termios_p); // deliberately ignore failure
|
||||||
free(termios_p);
|
free(termios_p);
|
||||||
safe_close(fd);
|
safe_close(fd, __FILE__, __LINE__);
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef TTY_ARGS
|
#undef TTY_ARGS
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
wcwidth_wrap(PyObject UNUSED *self, PyObject *chr) {
|
||||||
|
return PyLong_FromLong(wcwidth_std(PyLong_AsLong(chr)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static PyMethodDef module_methods[] = {
|
static PyMethodDef module_methods[] = {
|
||||||
|
{"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""},
|
||||||
|
{"wcswidth", (PyCFunction)wcswidth_std, METH_O, ""},
|
||||||
{"open_tty", open_tty, METH_VARARGS, ""},
|
{"open_tty", open_tty, METH_VARARGS, ""},
|
||||||
{"normal_tty", normal_tty, METH_VARARGS, ""},
|
{"normal_tty", normal_tty, METH_VARARGS, ""},
|
||||||
{"raw_tty", raw_tty, METH_VARARGS, ""},
|
{"raw_tty", raw_tty, METH_VARARGS, ""},
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ void log_error(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
|
|||||||
typedef unsigned long long id_type;
|
typedef unsigned long long id_type;
|
||||||
typedef uint32_t char_type;
|
typedef uint32_t char_type;
|
||||||
typedef uint32_t color_type;
|
typedef uint32_t color_type;
|
||||||
|
typedef uint16_t hyperlink_id_type;
|
||||||
|
#define HYPERLINK_MAX_NUMBER UINT16_MAX
|
||||||
typedef uint16_t combining_type;
|
typedef uint16_t combining_type;
|
||||||
typedef uint32_t pixel;
|
typedef uint32_t pixel;
|
||||||
typedef unsigned int index_type;
|
typedef unsigned int index_type;
|
||||||
@@ -160,6 +162,7 @@ typedef struct {
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
char_type ch;
|
char_type ch;
|
||||||
combining_type cc_idx[2];
|
combining_type cc_idx[2];
|
||||||
|
hyperlink_id_type hyperlink_id;
|
||||||
} CPUCell;
|
} CPUCell;
|
||||||
|
|
||||||
|
|
||||||
@@ -190,13 +193,20 @@ typedef struct {
|
|||||||
} HistoryBufSegment;
|
} HistoryBufSegment;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
index_type bufsize, maxsz;
|
uint8_t *buffer;
|
||||||
Py_UCS4 *buffer;
|
size_t buffer_size, max_sz;
|
||||||
index_type start, end;
|
size_t start, length;
|
||||||
index_type bufend;
|
|
||||||
bool rewrap_needed;
|
bool rewrap_needed;
|
||||||
} PagerHistoryBuf;
|
} PagerHistoryBuf;
|
||||||
|
|
||||||
|
typedef struct {int x;} *HYPERLINK_POOL_HANDLE;
|
||||||
|
typedef struct {
|
||||||
|
Py_UCS4 *buf;
|
||||||
|
size_t len, capacity;
|
||||||
|
HYPERLINK_POOL_HANDLE hyperlink_pool;
|
||||||
|
hyperlink_id_type active_hyperlink_id;
|
||||||
|
} ANSIBuf;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
PyObject_HEAD
|
PyObject_HEAD
|
||||||
|
|
||||||
@@ -311,6 +321,12 @@ void play_canberra_sound(const char *which_sound, const char *event_id);
|
|||||||
#endif
|
#endif
|
||||||
SPRITE_MAP_HANDLE alloc_sprite_map(unsigned int, unsigned int);
|
SPRITE_MAP_HANDLE alloc_sprite_map(unsigned int, unsigned int);
|
||||||
SPRITE_MAP_HANDLE free_sprite_map(SPRITE_MAP_HANDLE);
|
SPRITE_MAP_HANDLE free_sprite_map(SPRITE_MAP_HANDLE);
|
||||||
|
const char* get_hyperlink_for_id(const HYPERLINK_POOL_HANDLE, hyperlink_id_type id, bool only_url);
|
||||||
|
|
||||||
static inline void safe_close(int fd) { while(close(fd) != 0 && errno == EINTR); }
|
static inline void safe_close(int fd, const char* file UNUSED, const int line UNUSED) {
|
||||||
|
#if 0
|
||||||
|
printf("Closing fd: %d from file: %s line: %d\n", fd, file, line);
|
||||||
|
#endif
|
||||||
|
while(close(fd) != 0 && errno == EINTR);
|
||||||
|
}
|
||||||
void log_event(const char *format, ...) __attribute__((format(printf, 1, 2)));
|
void log_event(const char *format, ...) __attribute__((format(printf, 1, 2)));
|
||||||
|
|||||||
2
kitty/emoji.h
generated
2
kitty/emoji.h
generated
@@ -1,4 +1,4 @@
|
|||||||
// unicode data, built from the unicode standard on: 2020-04-06
|
// unicode data, built from the unicode standard on: 2020-09-22
|
||||||
// see gen-wcwidth.py
|
// see gen-wcwidth.py
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "data-types.h"
|
#include "data-types.h"
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ GLFW_CONTEXT_VERSION_MINOR: int
|
|||||||
GLFW_CONTEXT_REVISION: int
|
GLFW_CONTEXT_REVISION: int
|
||||||
GLFW_CONTEXT_ROBUSTNESS: int
|
GLFW_CONTEXT_ROBUSTNESS: int
|
||||||
GLFW_OPENGL_FORWARD_COMPAT: int
|
GLFW_OPENGL_FORWARD_COMPAT: int
|
||||||
GLFW_OPENGL_DEBUG_CONTEXT: int
|
GLFW_CONTEXT_DEBUG: int
|
||||||
GLFW_OPENGL_PROFILE: int
|
GLFW_OPENGL_PROFILE: int
|
||||||
GLFW_OPENGL_API: int
|
GLFW_OPENGL_API: int
|
||||||
GLFW_OPENGL_ES_API: int
|
GLFW_OPENGL_ES_API: int
|
||||||
@@ -461,7 +461,7 @@ def coretext_all_fonts() -> Tuple[CoreTextFont, ...]:
|
|||||||
|
|
||||||
|
|
||||||
def add_timer(
|
def add_timer(
|
||||||
callback: Callable[[Optional[int]], bool],
|
callback: Callable[[Optional[int]], None],
|
||||||
interval: float,
|
interval: float,
|
||||||
repeats: bool = True
|
repeats: bool = True
|
||||||
) -> int:
|
) -> int:
|
||||||
@@ -519,9 +519,8 @@ def dbus_send_notification(
|
|||||||
def cocoa_send_notification(
|
def cocoa_send_notification(
|
||||||
identifier: Optional[str],
|
identifier: Optional[str],
|
||||||
title: str,
|
title: str,
|
||||||
informative_text: str,
|
body: Optional[str],
|
||||||
path_to_img: Optional[str],
|
subtitle: Optional[str],
|
||||||
subtitle: Optional[str] = None
|
|
||||||
) -> None:
|
) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -781,6 +780,10 @@ def x11_window_id(os_window_id: int) -> int:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def cocoa_window_id(os_window_id: int) -> int:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def swap_tabs(os_window_id: int, a: int, b: int) -> None:
|
def swap_tabs(os_window_id: int, a: int, b: int) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -941,7 +944,10 @@ class HistoryBuf:
|
|||||||
def as_text(self, callback: Callable[[str], None], as_ansi: bool, insert_wrap_markers: bool) -> None:
|
def as_text(self, callback: Callable[[str], None], as_ansi: bool, insert_wrap_markers: bool) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def pagerhist_as_text(self, callback: Callable[[str], None]) -> None:
|
def pagerhist_as_text(self) -> str:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def pagerhist_as_bytes(self) -> bytes:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -1061,6 +1067,15 @@ class Screen:
|
|||||||
def erase_in_display(self, how: int = 0, private: bool = False) -> None:
|
def erase_in_display(self, how: int = 0, private: bool = False) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def focus_changed(self, focused: bool) -> bool:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def has_focus(self) -> bool:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def has_activity_since_last_focus(self) -> bool:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def set_tab_bar_render_data(
|
def set_tab_bar_render_data(
|
||||||
os_window_id: int, xstart: float, ystart: float, dx: float, dy: float,
|
os_window_id: int, xstart: float, ystart: float, dx: float, dy: float,
|
||||||
|
|||||||
@@ -130,12 +130,48 @@ font_group_is_unused(FontGroup *fg) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
free_maps(Font *font) {
|
||||||
|
#define free_a_map(type, attr) {\
|
||||||
|
type *s, *t; \
|
||||||
|
for (size_t i = 0; i < sizeof(font->attr)/sizeof(font->attr[0]); i++) { \
|
||||||
|
s = font->attr[i].next; \
|
||||||
|
while (s) { \
|
||||||
|
t = s; \
|
||||||
|
s = s->next; \
|
||||||
|
free(t); \
|
||||||
|
} \
|
||||||
|
}\
|
||||||
|
memset(font->attr, 0, sizeof(font->attr)); \
|
||||||
|
}
|
||||||
|
free_a_map(SpritePosition, sprite_map);
|
||||||
|
free_a_map(SpecialGlyphCache, special_glyph_cache);
|
||||||
|
#undef free_a_map
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
del_font(Font *f) {
|
||||||
|
Py_CLEAR(f->face);
|
||||||
|
free(f->ffs_hb_features); f->ffs_hb_features = NULL;
|
||||||
|
free_maps(f);
|
||||||
|
f->bold = false; f->italic = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
del_font_group(FontGroup *fg) {
|
||||||
|
free(fg->canvas); fg->canvas = NULL;
|
||||||
|
fg->sprite_map = free_sprite_map(fg->sprite_map);
|
||||||
|
for (size_t i = 0; i < fg->fonts_count; i++) del_font(fg->fonts + i);
|
||||||
|
free(fg->fonts); fg->fonts = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
trim_unused_font_groups(void) {
|
trim_unused_font_groups(void) {
|
||||||
save_window_font_groups();
|
save_window_font_groups();
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
while (i < num_font_groups) {
|
while (i < num_font_groups) {
|
||||||
if (font_group_is_unused(font_groups + i)) {
|
if (font_group_is_unused(font_groups + i)) {
|
||||||
|
del_font_group(font_groups + i);
|
||||||
size_t num_to_right = (--num_font_groups) - i;
|
size_t num_to_right = (--num_font_groups) - i;
|
||||||
if (!num_to_right) break;
|
if (!num_to_right) break;
|
||||||
memmove(font_groups + i, font_groups + 1 + i, num_to_right * sizeof(FontGroup));
|
memmove(font_groups + i, font_groups + 1 + i, num_to_right * sizeof(FontGroup));
|
||||||
@@ -176,7 +212,7 @@ font_group_for(double font_sz_in_pts, double logical_dpi_x, double logical_dpi_y
|
|||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
clear_canvas(FontGroup *fg) {
|
clear_canvas(FontGroup *fg) {
|
||||||
if (fg->canvas) memset(fg->canvas, 0, CELLS_IN_CANVAS * fg->cell_width * fg->cell_height * sizeof(pixel));
|
if (fg->canvas) memset(fg->canvas, 0, sizeof(pixel) * CELLS_IN_CANVAS * fg->cell_width * fg->cell_height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -282,25 +318,6 @@ sprite_tracker_current_layout(FONTS_DATA_HANDLE data, unsigned int *x, unsigned
|
|||||||
*x = fg->sprite_tracker.xnum; *y = fg->sprite_tracker.ynum; *z = fg->sprite_tracker.z;
|
*x = fg->sprite_tracker.xnum; *y = fg->sprite_tracker.ynum; *z = fg->sprite_tracker.z;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
free_maps(Font *font) {
|
|
||||||
#define free_a_map(type, attr) {\
|
|
||||||
type *s, *t; \
|
|
||||||
for (size_t i = 0; i < sizeof(font->attr)/sizeof(font->attr[0]); i++) { \
|
|
||||||
s = font->attr[i].next; \
|
|
||||||
while (s) { \
|
|
||||||
t = s; \
|
|
||||||
s = s->next; \
|
|
||||||
free(t); \
|
|
||||||
} \
|
|
||||||
}\
|
|
||||||
memset(font->attr, 0, sizeof(font->attr)); \
|
|
||||||
}
|
|
||||||
free_a_map(SpritePosition, sprite_map);
|
|
||||||
free_a_map(SpecialGlyphCache, special_glyph_cache);
|
|
||||||
#undef free_a_map
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
clear_sprite_map(Font *font) {
|
clear_sprite_map(Font *font) {
|
||||||
#define CLEAR(s) s->filled = false; s->rendered = false; s->colored = false; s->glyph = 0; zero_at_ptr(&s->extra_glyphs); s->x = 0; s->y = 0; s->z = 0; s->ligature_index = 0;
|
#define CLEAR(s) s->filled = false; s->rendered = false; s->colored = false; s->glyph = 0; zero_at_ptr(&s->extra_glyphs); s->x = 0; s->y = 0; s->z = 0; s->ligature_index = 0;
|
||||||
@@ -384,22 +401,6 @@ init_font(Font *f, PyObject *face, bool bold, bool italic, bool emoji_presentati
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
|
||||||
del_font(Font *f) {
|
|
||||||
Py_CLEAR(f->face);
|
|
||||||
free(f->ffs_hb_features); f->ffs_hb_features = NULL;
|
|
||||||
free_maps(f);
|
|
||||||
f->bold = false; f->italic = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
del_font_group(FontGroup *fg) {
|
|
||||||
free(fg->canvas); fg->canvas = NULL;
|
|
||||||
fg->sprite_map = free_sprite_map(fg->sprite_map);
|
|
||||||
for (size_t i = 0; i < fg->fonts_count; i++) del_font(fg->fonts + i);
|
|
||||||
free(fg->fonts); fg->fonts = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
free_font_groups(void) {
|
free_font_groups(void) {
|
||||||
if (font_groups) {
|
if (font_groups) {
|
||||||
@@ -457,7 +458,7 @@ calc_cell_metrics(FontGroup *fg) {
|
|||||||
fg->cell_width = cell_width; fg->cell_height = cell_height;
|
fg->cell_width = cell_width; fg->cell_height = cell_height;
|
||||||
fg->baseline = baseline; fg->underline_position = underline_position; fg->underline_thickness = underline_thickness, fg->strikethrough_position = strikethrough_position, fg->strikethrough_thickness = strikethrough_thickness;
|
fg->baseline = baseline; fg->underline_position = underline_position; fg->underline_thickness = underline_thickness, fg->strikethrough_position = strikethrough_position, fg->strikethrough_thickness = strikethrough_thickness;
|
||||||
free(fg->canvas);
|
free(fg->canvas);
|
||||||
fg->canvas = calloc(CELLS_IN_CANVAS * fg->cell_width * fg->cell_height, sizeof(pixel));
|
fg->canvas = calloc((size_t)CELLS_IN_CANVAS * fg->cell_width * fg->cell_height, sizeof(pixel));
|
||||||
if (!fg->canvas) fatal("Out of memory allocating canvas for font group");
|
if (!fg->canvas) fatal("Out of memory allocating canvas for font group");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -600,6 +601,8 @@ START_ALLOW_CASE_RANGE
|
|||||||
case 0xe0ba: //
|
case 0xe0ba: //
|
||||||
case 0xe0bc: //
|
case 0xe0bc: //
|
||||||
case 0xe0be: //
|
case 0xe0be: //
|
||||||
|
case 0x1fb00 ... 0x1fb8b: // symbols for legacy computing
|
||||||
|
case 0x1fba0 ... 0x1fbae:
|
||||||
return BOX_FONT;
|
return BOX_FONT;
|
||||||
default:
|
default:
|
||||||
ans = in_symbol_maps(fg, cpu_cell->ch);
|
ans = in_symbol_maps(fg, cpu_cell->ch);
|
||||||
@@ -637,8 +640,12 @@ START_ALLOW_CASE_RANGE
|
|||||||
return ch - 0x2500; // IDs from 0x00 to 0x9f
|
return ch - 0x2500; // IDs from 0x00 to 0x9f
|
||||||
case 0xe0b0 ... 0xe0d4:
|
case 0xe0b0 ... 0xe0d4:
|
||||||
return 0xa0 + ch - 0xe0b0; // IDs from 0xa0 to 0xc4
|
return 0xa0 + ch - 0xe0b0; // IDs from 0xa0 to 0xc4
|
||||||
|
case 0x1fb00 ... 0x1fb8b:
|
||||||
|
return 0xc5 + ch - 0x1fb00; // IDs from 0xc5 to 0x150
|
||||||
|
case 0x1fba0 ... 0x1fbae:
|
||||||
|
return 0x151 + ch - 0x1fba0;
|
||||||
default:
|
default:
|
||||||
return 0xff;
|
return 0xffff;
|
||||||
}
|
}
|
||||||
END_ALLOW_CASE_RANGE
|
END_ALLOW_CASE_RANGE
|
||||||
}
|
}
|
||||||
@@ -1132,6 +1139,11 @@ render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, inde
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
is_non_emoji_dingbat(char_type ch) {
|
||||||
|
return 0x2700 <= ch && ch <= 0x27bf && !is_emoji(ch);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
render_line(FONTS_DATA_HANDLE fg_, Line *line, index_type lnum, Cursor *cursor, DisableLigature disable_ligature_strategy) {
|
render_line(FONTS_DATA_HANDLE fg_, Line *line, index_type lnum, Cursor *cursor, DisableLigature disable_ligature_strategy) {
|
||||||
#define RENDER if (run_font_idx != NO_FONT && i > first_cell_in_run) { \
|
#define RENDER if (run_font_idx != NO_FONT && i > first_cell_in_run) { \
|
||||||
@@ -1154,7 +1166,7 @@ render_line(FONTS_DATA_HANDLE fg_, Line *line, index_type lnum, Cursor *cursor,
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
cell_font_idx != MISSING_FONT &&
|
cell_font_idx != MISSING_FONT &&
|
||||||
((is_fallback_font && !is_emoji_presentation && is_symbol(cpu_cell->ch)) || (cell_font_idx != BOX_FONT && is_private_use(cpu_cell->ch)))
|
((is_fallback_font && !is_emoji_presentation && is_symbol(cpu_cell->ch)) || (cell_font_idx != BOX_FONT && (is_private_use(cpu_cell->ch))) || is_non_emoji_dingbat(cpu_cell->ch))
|
||||||
) {
|
) {
|
||||||
unsigned int desired_cells = 1;
|
unsigned int desired_cells = 1;
|
||||||
if (cell_font_idx > 0) {
|
if (cell_font_idx > 0) {
|
||||||
@@ -1168,7 +1180,7 @@ render_line(FONTS_DATA_HANDLE fg_, Line *line, index_type lnum, Cursor *cursor,
|
|||||||
unsigned int num_spaces = 0;
|
unsigned int num_spaces = 0;
|
||||||
while ((line->cpu_cells[i+num_spaces+1].ch == ' ')
|
while ((line->cpu_cells[i+num_spaces+1].ch == ' ')
|
||||||
&& num_spaces < MAX_NUM_EXTRA_GLYPHS_PUA
|
&& num_spaces < MAX_NUM_EXTRA_GLYPHS_PUA
|
||||||
&& num_spaces < desired_cells
|
&& num_spaces + 1 < desired_cells
|
||||||
&& i + num_spaces + 1 < line->xnum) {
|
&& i + num_spaces + 1 < line->xnum) {
|
||||||
num_spaces++;
|
num_spaces++;
|
||||||
// We have a private use char followed by space(s), render it as a multi-cell ligature.
|
// We have a private use char followed by space(s), render it as a multi-cell ligature.
|
||||||
@@ -1388,7 +1400,7 @@ concat_cells(PyObject UNUSED *self, PyObject *args) {
|
|||||||
PyObject *cells;
|
PyObject *cells;
|
||||||
if (!PyArg_ParseTuple(args, "IIpO!", &cell_width, &cell_height, &is_32_bit, &PyTuple_Type, &cells)) return NULL;
|
if (!PyArg_ParseTuple(args, "IIpO!", &cell_width, &cell_height, &is_32_bit, &PyTuple_Type, &cells)) return NULL;
|
||||||
size_t num_cells = PyTuple_GET_SIZE(cells), r, c, i;
|
size_t num_cells = PyTuple_GET_SIZE(cells), r, c, i;
|
||||||
PyObject *ans = PyBytes_FromStringAndSize(NULL, 4 * cell_width * cell_height * num_cells);
|
PyObject *ans = PyBytes_FromStringAndSize(NULL, (size_t)4 * cell_width * cell_height * num_cells);
|
||||||
if (ans == NULL) return PyErr_NoMemory();
|
if (ans == NULL) return PyErr_NoMemory();
|
||||||
pixel *dest = (pixel*)PyBytes_AS_STRING(ans);
|
pixel *dest = (pixel*)PyBytes_AS_STRING(ans);
|
||||||
for (r = 0; r < cell_height; r++) {
|
for (r = 0; r < cell_height; r++) {
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
import math
|
import math
|
||||||
from functools import partial as p
|
from functools import partial as p, wraps
|
||||||
from itertools import repeat
|
from itertools import repeat
|
||||||
from typing import (
|
from typing import (
|
||||||
Callable, Dict, Generator, Iterable, List, MutableSequence, Optional,
|
Any, Callable, Dict, Generator, Iterable, List, MutableSequence, Optional,
|
||||||
Sequence, Tuple, cast
|
Sequence, Tuple, cast
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -146,18 +146,50 @@ def cross(buf: BufType, width: int, height: int, a: int = 1, b: int = 1, c: int
|
|||||||
half_vline(buf, width, height, level=d, which='bottom')
|
half_vline(buf, width, height, level=d, which='bottom')
|
||||||
|
|
||||||
|
|
||||||
def fill_region(buf: BufType, width: int, height: int, xlimits: Iterable[Iterable[float]]) -> None:
|
def downsample(src: BufType, dest: BufType, dest_width: int, dest_height: int, factor: int = 4) -> None:
|
||||||
|
src_width = 4 * dest_width
|
||||||
|
|
||||||
|
def average_intensity_in_src(dest_x: int, dest_y: int) -> int:
|
||||||
|
src_y = dest_y * factor
|
||||||
|
src_x = dest_x * factor
|
||||||
|
total = 0
|
||||||
|
for y in range(src_y, src_y + factor):
|
||||||
|
offset = src_width * y
|
||||||
|
for x in range(src_x, src_x + factor):
|
||||||
|
total += src[offset + x]
|
||||||
|
return total // (factor * factor)
|
||||||
|
|
||||||
|
for y in range(dest_height):
|
||||||
|
offset = dest_width * y
|
||||||
|
for x in range(dest_width):
|
||||||
|
dest[offset + x] = min(255, dest[offset + x] + average_intensity_in_src(x, y))
|
||||||
|
|
||||||
|
|
||||||
|
def supersampled(supersample_factor: int = 4) -> Callable:
|
||||||
|
# Anti-alias the drawing performed by the wrapped function by
|
||||||
|
# using supersampling
|
||||||
|
|
||||||
|
class SSByteArray(bytearray):
|
||||||
|
supersample_factor = 1
|
||||||
|
|
||||||
|
def create_wrapper(f: Callable) -> Callable:
|
||||||
|
@wraps(f)
|
||||||
|
def supersampled_wrapper(buf: BufType, width: int, height: int, *args: Any, **kw: Any) -> None:
|
||||||
|
w, h = supersample_factor * width, supersample_factor * height
|
||||||
|
ssbuf = SSByteArray(w * h)
|
||||||
|
ssbuf.supersample_factor = supersample_factor
|
||||||
|
f(ssbuf, w, h, *args, **kw)
|
||||||
|
downsample(ssbuf, buf, width, height, factor=supersample_factor)
|
||||||
|
return supersampled_wrapper
|
||||||
|
return create_wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def fill_region(buf: BufType, width: int, height: int, xlimits: Iterable[Iterable[float]], inverted: bool = False) -> None:
|
||||||
|
full, empty = (0, 255) if inverted else (255, 0)
|
||||||
for y in range(height):
|
for y in range(height):
|
||||||
offset = y * width
|
offset = y * width
|
||||||
for x, (upper, lower) in enumerate(xlimits):
|
for x, (upper, lower) in enumerate(xlimits):
|
||||||
buf[x + offset] = 255 if upper <= y <= lower else 0
|
buf[x + offset] = full if upper <= y <= lower else empty
|
||||||
# Anti-alias the boundary, simple y-axis anti-aliasing
|
|
||||||
for x, limits in enumerate(xlimits):
|
|
||||||
for yf in limits:
|
|
||||||
for ypx in range(int(math.floor(yf)), int(math.ceil(yf)) + 1):
|
|
||||||
if 0 <= ypx < height:
|
|
||||||
off = ypx * width + x
|
|
||||||
buf[off] = min(255, buf[off] + int((1 - abs(yf - ypx)) * 255))
|
|
||||||
|
|
||||||
|
|
||||||
def line_equation(x1: int, y1: int, x2: int, y2: int) -> Callable[[int], float]:
|
def line_equation(x1: int, y1: int, x2: int, y2: int) -> Callable[[int], float]:
|
||||||
@@ -170,6 +202,7 @@ def line_equation(x1: int, y1: int, x2: int, y2: int) -> Callable[[int], float]:
|
|||||||
return y
|
return y
|
||||||
|
|
||||||
|
|
||||||
|
@supersampled()
|
||||||
def triangle(buf: BufType, width: int, height: int, left: bool = True) -> None:
|
def triangle(buf: BufType, width: int, height: int, left: bool = True) -> None:
|
||||||
ay1, by1, y2 = 0, height - 1, height // 2
|
ay1, by1, y2 = 0, height - 1, height // 2
|
||||||
if left:
|
if left:
|
||||||
@@ -182,6 +215,7 @@ def triangle(buf: BufType, width: int, height: int, left: bool = True) -> None:
|
|||||||
fill_region(buf, width, height, xlimits)
|
fill_region(buf, width, height, xlimits)
|
||||||
|
|
||||||
|
|
||||||
|
@supersampled()
|
||||||
def corner_triangle(buf: BufType, width: int, height: int, corner: str) -> None:
|
def corner_triangle(buf: BufType, width: int, height: int, corner: str) -> None:
|
||||||
if corner == 'top-right' or corner == 'bottom-left':
|
if corner == 'top-right' or corner == 'bottom-left':
|
||||||
diagonal_y = line_equation(0, 0, width - 1, height - 1)
|
diagonal_y = line_equation(0, 0, width - 1, height - 1)
|
||||||
@@ -198,85 +232,61 @@ def corner_triangle(buf: BufType, width: int, height: int, corner: str) -> None:
|
|||||||
fill_region(buf, width, height, xlimits)
|
fill_region(buf, width, height, xlimits)
|
||||||
|
|
||||||
|
|
||||||
def antialiased_1px_line(buf: BufType, width: int, height: int, p1: Tuple[int, int], p2: Tuple[int, int]) -> None:
|
@supersampled()
|
||||||
# Draw an antialiased line using the Wu algorithm
|
def half_triangle(buf: BufType, width: int, height: int, which: str = 'left', inverted: bool = False) -> None:
|
||||||
x1, y1 = p1
|
mid_x, mid_y = width // 2, height // 2
|
||||||
x2, y2 = p2
|
if which == 'left':
|
||||||
dx, dy = x2 - x1, y2 - y1
|
upper_y = line_equation(0, 0, mid_x, mid_y)
|
||||||
off_limit = height * width
|
lower_y = line_equation(0, height - 1, mid_x, mid_y)
|
||||||
steep = abs(dx) < abs(dy)
|
limits = tuple((upper_y(x), lower_y(x)) for x in range(width))
|
||||||
|
elif which == 'top':
|
||||||
if steep:
|
first_y = line_equation(0, 0, mid_x, mid_y)
|
||||||
x1, y1, x2, y2, dx, dy = y1, x1, y2, x2, dy, dx
|
first = tuple((0, first_y(x)) for x in range(mid_x))
|
||||||
|
second_y = line_equation(mid_x, mid_y, width - 1, 0)
|
||||||
def p(x: int, y: int) -> Tuple[int, int]:
|
second = tuple((0, second_y(x)) for x in range(mid_x, width))
|
||||||
return y, x
|
limits = first + second
|
||||||
else:
|
elif which == 'right':
|
||||||
def p(x: int, y: int) -> Tuple[int, int]:
|
upper_y = line_equation(mid_x, mid_y, width - 1, 0)
|
||||||
return x, y
|
lower_y = line_equation(mid_x, mid_y, width - 1, height - 1)
|
||||||
|
limits = tuple((upper_y(x), lower_y(x)) for x in range(width))
|
||||||
if x2 < x1:
|
elif which == 'bottom':
|
||||||
x1, x2, y1, y2 = x2, x1, y2, y1
|
first_y = line_equation(0, height - 1, mid_x, mid_y)
|
||||||
|
first_ = tuple((first_y(x), height - 1) for x in range(mid_x))
|
||||||
def fpart(x: float) -> float:
|
second_y = line_equation(mid_x, mid_y, width - 1, height - 1)
|
||||||
return x - int(x)
|
second_ = tuple((second_y(x), height - 1) for x in range(mid_x, width))
|
||||||
|
limits = first_ + second_
|
||||||
def rfpart(x: float) -> float:
|
fill_region(buf, width, height, limits, inverted)
|
||||||
return 1 - fpart(x)
|
|
||||||
|
|
||||||
def putpixel(p: Tuple[int, int], alpha: float) -> None:
|
|
||||||
x, y = p
|
|
||||||
off = int(x + y * width)
|
|
||||||
if 0 <= off < off_limit:
|
|
||||||
buf[off] = int(min(buf[off] + (alpha * 255), 255))
|
|
||||||
|
|
||||||
def draw_endpoint(pt: Tuple[int, int]) -> int:
|
|
||||||
x, y = pt
|
|
||||||
xend = round(x)
|
|
||||||
yend = y + grad * (xend - x)
|
|
||||||
xgap = rfpart(x + 0.5)
|
|
||||||
px, py = int(xend), int(yend)
|
|
||||||
putpixel(p(px, py), rfpart(yend) * xgap)
|
|
||||||
putpixel(p(px, py+1), fpart(yend) * xgap)
|
|
||||||
return px
|
|
||||||
|
|
||||||
grad = dy/dx
|
|
||||||
intery = y1 + rfpart(x1) * grad
|
|
||||||
|
|
||||||
xstart = draw_endpoint(p(*p1))
|
|
||||||
xend = draw_endpoint(p(*p2))
|
|
||||||
|
|
||||||
if xstart > xend:
|
|
||||||
xstart, xend = xend, xstart
|
|
||||||
xstart += 1
|
|
||||||
|
|
||||||
for x in range(xstart, xend):
|
|
||||||
y = int(intery)
|
|
||||||
putpixel(p(x, y), rfpart(intery))
|
|
||||||
putpixel(p(x, y+1), fpart(intery))
|
|
||||||
intery += grad
|
|
||||||
|
|
||||||
|
|
||||||
def antialiased_line(buf: BufType, width: int, height: int, p1: Tuple[int, int], p2: Tuple[int, int], level: int = 1) -> None:
|
def thick_line(buf: BufType, width: int, height: int, thickness_in_pixels: int, p1: Tuple[int, int], p2: Tuple[int, int]) -> None:
|
||||||
th = thickness(level)
|
if p1[0] > p2[0]:
|
||||||
if th < 2:
|
p1, p2 = p2, p1
|
||||||
return antialiased_1px_line(buf, width, height, p1, p2)
|
leq = line_equation(*p1, *p2)
|
||||||
(x1, y1), (x2, y2) = p1, p2
|
delta, extra = divmod(thickness_in_pixels, 2)
|
||||||
dh = th // 2
|
|
||||||
items = range(-dh, dh + (th % 2))
|
for x in range(p1[0], p2[0] + 1):
|
||||||
for delta in items:
|
if 0 <= x < width:
|
||||||
antialiased_1px_line(buf, width, height, (x1, y1 + delta), (x2, y2 + delta))
|
y_p = leq(x)
|
||||||
|
r = range(int(y_p) - delta, int(y_p) + delta + extra)
|
||||||
|
for y in r:
|
||||||
|
if 0 <= y < height:
|
||||||
|
buf[x + y * width] = 255
|
||||||
|
|
||||||
|
|
||||||
|
@supersampled()
|
||||||
def cross_line(buf: BufType, width: int, height: int, left: bool = True, level: int = 1) -> None:
|
def cross_line(buf: BufType, width: int, height: int, left: bool = True, level: int = 1) -> None:
|
||||||
if left:
|
if left:
|
||||||
p1, p2 = (0, 0), (width - 1, height - 1)
|
p1, p2 = (0, 0), (width - 1, height - 1)
|
||||||
else:
|
else:
|
||||||
p1, p2 = (width - 1, 0), (0, height - 1)
|
p1, p2 = (width - 1, 0), (0, height - 1)
|
||||||
antialiased_line(buf, width, height, p1, p2, level=level)
|
supersample_factor = getattr(buf, 'supersample_factor')
|
||||||
|
thick_line(buf, width, height, supersample_factor * thickness(level), p1, p2)
|
||||||
|
|
||||||
|
|
||||||
|
@supersampled()
|
||||||
def half_cross_line(buf: BufType, width: int, height: int, which: str = 'tl', level: int = 1) -> None:
|
def half_cross_line(buf: BufType, width: int, height: int, which: str = 'tl', level: int = 1) -> None:
|
||||||
|
supersample_factor = getattr(buf, 'supersample_factor')
|
||||||
|
thickness_in_pixels = thickness(level) * supersample_factor
|
||||||
my = (height - 1) // 2
|
my = (height - 1) // 2
|
||||||
if which == 'tl':
|
if which == 'tl':
|
||||||
p1 = 0, 0
|
p1 = 0, 0
|
||||||
@@ -290,7 +300,27 @@ def half_cross_line(buf: BufType, width: int, height: int, which: str = 'tl', le
|
|||||||
else:
|
else:
|
||||||
p2 = width - 1, height - 1
|
p2 = width - 1, height - 1
|
||||||
p1 = 0, my
|
p1 = 0, my
|
||||||
antialiased_line(buf, width, height, p1, p2, level=level)
|
thick_line(buf, width, height, thickness_in_pixels, p1, p2)
|
||||||
|
|
||||||
|
|
||||||
|
@supersampled()
|
||||||
|
def mid_lines(buf: BufType, width: int, height: int, level: int = 1, pts: Iterable[str] = ('lt',)) -> None:
|
||||||
|
mid_x, mid_y = width // 2, height // 2
|
||||||
|
supersample_factor = getattr(buf, 'supersample_factor')
|
||||||
|
|
||||||
|
def pt_to_coords(p: str) -> Tuple[int, int]:
|
||||||
|
if p == 'l':
|
||||||
|
return 0, mid_y
|
||||||
|
if p == 't':
|
||||||
|
return mid_x, 0
|
||||||
|
if p == 'r':
|
||||||
|
return width - 1, mid_y
|
||||||
|
if p == 'b':
|
||||||
|
return mid_x, height - 1
|
||||||
|
|
||||||
|
for x in pts:
|
||||||
|
p1, p2 = map(pt_to_coords, x)
|
||||||
|
thick_line(buf, width, height, supersample_factor * thickness(level), p1, p2)
|
||||||
|
|
||||||
|
|
||||||
BezierFunc = Callable[[float], float]
|
BezierFunc = Callable[[float], float]
|
||||||
@@ -360,6 +390,7 @@ def get_bezier_limits(bezier_x: BezierFunc, bezier_y: BezierFunc) -> Generator[T
|
|||||||
yield upper, lower
|
yield upper, lower
|
||||||
|
|
||||||
|
|
||||||
|
@supersampled()
|
||||||
def D(buf: BufType, width: int, height: int, left: bool = True) -> None:
|
def D(buf: BufType, width: int, height: int, left: bool = True) -> None:
|
||||||
c1x = find_bezier_for_D(width, height)
|
c1x = find_bezier_for_D(width, height)
|
||||||
start = (0, 0)
|
start = (0, 0)
|
||||||
@@ -380,6 +411,54 @@ def D(buf: BufType, width: int, height: int, left: bool = True) -> None:
|
|||||||
buf[offset + dest_x] = mbuf[offset + src_x]
|
buf[offset + dest_x] = mbuf[offset + src_x]
|
||||||
|
|
||||||
|
|
||||||
|
def draw_parametrized_curve(buf: BufType, width: int, height: int, delta: int, extra: int, xfunc: BezierFunc, yfunc: BezierFunc) -> None:
|
||||||
|
num_samples = height*4
|
||||||
|
seen = set()
|
||||||
|
for i in range(num_samples + 1):
|
||||||
|
t = (i / num_samples)
|
||||||
|
p = x_p, y_p = int(xfunc(t)), int(yfunc(t))
|
||||||
|
if p in seen:
|
||||||
|
continue
|
||||||
|
seen.add(p)
|
||||||
|
for y in range(y_p - delta, y_p + delta + extra):
|
||||||
|
if 0 <= y < height:
|
||||||
|
offset = y * width
|
||||||
|
for x in range(x_p - delta, x_p + delta + extra):
|
||||||
|
if 0 <= x < width:
|
||||||
|
pos = offset + x
|
||||||
|
buf[pos] = min(255, buf[pos] + 255)
|
||||||
|
|
||||||
|
|
||||||
|
@supersampled()
|
||||||
|
def rounded_corner(buf: BufType, width: int, height: int, level: int = 1, which: str = '╭') -> None:
|
||||||
|
supersample_factor = getattr(buf, 'supersample_factor')
|
||||||
|
delta, extra = divmod(thickness(level), 2)
|
||||||
|
hw = ((width / supersample_factor) // 2) * supersample_factor
|
||||||
|
hh = ((height / supersample_factor) // 2) * supersample_factor
|
||||||
|
if which == '╭':
|
||||||
|
start = hw, height - 1
|
||||||
|
end = width - 1, hh
|
||||||
|
c1 = hw, int(0.75 * height)
|
||||||
|
c2 = hw, hh + 1
|
||||||
|
elif which == '╮':
|
||||||
|
start = 0, hh
|
||||||
|
end = hw, height - 1
|
||||||
|
c1 = hw, hh + 1
|
||||||
|
c2 = hw, int(0.75 * height)
|
||||||
|
elif which == '╰':
|
||||||
|
start = width // 2, 0
|
||||||
|
end = width - 1, hh
|
||||||
|
c1 = hw, int(0.25 * height)
|
||||||
|
c2 = hw, hh - 1
|
||||||
|
elif which == '╯':
|
||||||
|
start = 0, hh
|
||||||
|
end = hw, 0
|
||||||
|
c1 = hw, hh - 1
|
||||||
|
c2 = hw, int(0.25 * height)
|
||||||
|
xfunc, yfunc = cubic_bezier(start, end, c1, c2)
|
||||||
|
draw_parametrized_curve(buf, width, height, delta * supersample_factor, extra * supersample_factor, xfunc, yfunc)
|
||||||
|
|
||||||
|
|
||||||
def half_dhline(buf: BufType, width: int, height: int, level: int = 1, which: str = 'left', only: Optional[str] = None) -> Tuple[int, int]:
|
def half_dhline(buf: BufType, width: int, height: int, level: int = 1, which: str = 'left', only: Optional[str] = None) -> Tuple[int, int]:
|
||||||
x1, x2 = (0, width // 2) if which == 'left' else (width // 2, width)
|
x1, x2 = (0, width // 2) if which == 'left' else (width // 2, width)
|
||||||
gap = thickness(level + 1, horizontal=False)
|
gap = thickness(level + 1, horizontal=False)
|
||||||
@@ -481,24 +560,6 @@ def inner_corner(buf: BufType, width: int, height: int, which: str = 'tl', level
|
|||||||
draw_vline(buf, width, y1, y2, width // 2 + (xd * hgap), level)
|
draw_vline(buf, width, y1, y2, width // 2 + (xd * hgap), level)
|
||||||
|
|
||||||
|
|
||||||
def vblock(buf: BufType, width: int, height: int, frac: float = 1., gravity: str = 'top') -> None:
|
|
||||||
num_rows = min(height, round(frac * height))
|
|
||||||
start = 0 if gravity == 'top' else height - num_rows
|
|
||||||
for r in range(start, start + num_rows):
|
|
||||||
off = r * width
|
|
||||||
for c in range(off, off + width):
|
|
||||||
buf[c] = 255
|
|
||||||
|
|
||||||
|
|
||||||
def hblock(buf: BufType, width: int, height: int, frac: float = 1., gravity: str = 'left') -> None:
|
|
||||||
num_cols = min(width, round(frac * width))
|
|
||||||
start = 0 if gravity == 'left' else width - num_cols
|
|
||||||
for r in range(height):
|
|
||||||
off = r * width + start
|
|
||||||
for c in range(off, off + num_cols):
|
|
||||||
buf[c] = 255
|
|
||||||
|
|
||||||
|
|
||||||
def shade(buf: BufType, width: int, height: int, light: bool = False, invert: bool = False) -> None:
|
def shade(buf: BufType, width: int, height: int, light: bool = False, invert: bool = False) -> None:
|
||||||
square_sz = max(1, width // 12)
|
square_sz = max(1, width // 12)
|
||||||
number_of_rows = height // square_sz
|
number_of_rows = height // square_sz
|
||||||
@@ -550,6 +611,80 @@ def quad(buf: BufType, width: int, height: int, x: int = 0, y: int = 0) -> None:
|
|||||||
buf[off + c] = 255
|
buf[off + c] = 255
|
||||||
|
|
||||||
|
|
||||||
|
def sextant(buf: BufType, width: int, height: int, level: int = 1, which: int = 0) -> None:
|
||||||
|
|
||||||
|
def draw_sextant(row: int = 0, col: int = 0) -> None:
|
||||||
|
if row == 0:
|
||||||
|
y_start, y_end = 0, height // 3
|
||||||
|
elif row == 1:
|
||||||
|
y_start, y_end = height // 3, 2 * height // 3
|
||||||
|
else:
|
||||||
|
y_start, y_end = 2 * height // 3, height
|
||||||
|
if col == 0:
|
||||||
|
x_start, x_end = 0, width // 2
|
||||||
|
else:
|
||||||
|
x_start, x_end = width // 2, width
|
||||||
|
for r in range(y_start, y_end):
|
||||||
|
off = r * width
|
||||||
|
for c in range(x_start, x_end):
|
||||||
|
buf[c + off] = 255
|
||||||
|
|
||||||
|
def add_row(q: int, r: int) -> None:
|
||||||
|
if q & 1:
|
||||||
|
draw_sextant(r)
|
||||||
|
if q & 2:
|
||||||
|
draw_sextant(r, col=1)
|
||||||
|
|
||||||
|
add_row(which % 4, 0)
|
||||||
|
add_row(which // 4, 1)
|
||||||
|
add_row(which // 16, 2)
|
||||||
|
|
||||||
|
|
||||||
|
@supersampled()
|
||||||
|
def smooth_mosaic(
|
||||||
|
buf: BufType, width: int, height: int, level: int = 1,
|
||||||
|
lower: bool = True, a: Tuple[float, float] = (0, 0), b: Tuple[float, float] = (0, 0)
|
||||||
|
) -> None:
|
||||||
|
ax, ay = int(a[0] * (width - 1)), int(a[1] * (height - 1))
|
||||||
|
bx, by = int(b[0] * (width - 1)), int(b[1] * (height - 1))
|
||||||
|
line = line_equation(ax, ay, bx, by)
|
||||||
|
|
||||||
|
def lower_condition(x: int, y: int) -> bool:
|
||||||
|
return y >= line(x)
|
||||||
|
|
||||||
|
def upper_condition(x: int, y: int) -> bool:
|
||||||
|
return y <= line(x)
|
||||||
|
|
||||||
|
condition = lower_condition if lower else upper_condition
|
||||||
|
for y in range(height):
|
||||||
|
offset = width * y
|
||||||
|
for x in range(width):
|
||||||
|
if condition(x, y):
|
||||||
|
buf[offset + x] = 255
|
||||||
|
|
||||||
|
|
||||||
|
def eight_bar(buf: BufType, width: int, height: int, level: int = 1, which: int = 0, horizontal: bool = False) -> None:
|
||||||
|
if horizontal:
|
||||||
|
x_range = range(0, width)
|
||||||
|
thickness = height // 8
|
||||||
|
y_start = min(which * thickness, height - 2)
|
||||||
|
y_range = range(y_start, height if which == 7 else min(y_start + thickness, height))
|
||||||
|
else:
|
||||||
|
y_range = range(0, height)
|
||||||
|
thickness = width // 8
|
||||||
|
x_start = min(which * thickness, width - 2)
|
||||||
|
x_range = range(x_start, width if which == 7 else min(x_start + thickness, width))
|
||||||
|
for y in y_range:
|
||||||
|
offset = y * width
|
||||||
|
for x in x_range:
|
||||||
|
buf[offset + x] = 255
|
||||||
|
|
||||||
|
|
||||||
|
def eight_block(buf: BufType, width: int, height: int, level: int = 1, which: Tuple[int, ...] = (0,), horizontal: bool = False) -> None:
|
||||||
|
for x in which:
|
||||||
|
eight_bar(buf, width, height, level, x, horizontal)
|
||||||
|
|
||||||
|
|
||||||
box_chars: Dict[str, List[Callable]] = {
|
box_chars: Dict[str, List[Callable]] = {
|
||||||
'─': [hline],
|
'─': [hline],
|
||||||
'━': [p(hline, level=3)],
|
'━': [p(hline, level=3)],
|
||||||
@@ -591,42 +726,54 @@ box_chars: Dict[str, List[Callable]] = {
|
|||||||
'': [p(corner_triangle, corner='top-right')],
|
'': [p(corner_triangle, corner='top-right')],
|
||||||
'═': [dhline],
|
'═': [dhline],
|
||||||
'║': [dvline],
|
'║': [dvline],
|
||||||
|
|
||||||
'╞': [vline, p(half_dhline, which='right')],
|
'╞': [vline, p(half_dhline, which='right')],
|
||||||
|
|
||||||
'╡': [vline, half_dhline],
|
'╡': [vline, half_dhline],
|
||||||
|
|
||||||
'╥': [hline, p(half_dvline, which='bottom')],
|
'╥': [hline, p(half_dvline, which='bottom')],
|
||||||
|
|
||||||
'╨': [hline, half_dvline],
|
'╨': [hline, half_dvline],
|
||||||
|
|
||||||
'╪': [vline, half_dhline, p(half_dhline, which='right')],
|
'╪': [vline, half_dhline, p(half_dhline, which='right')],
|
||||||
|
|
||||||
'╫': [hline, half_dvline, p(half_dvline, which='bottom')],
|
'╫': [hline, half_dvline, p(half_dvline, which='bottom')],
|
||||||
|
|
||||||
'╬': [p(inner_corner, which=x) for x in 'tl tr bl br'.split()],
|
'╬': [p(inner_corner, which=x) for x in 'tl tr bl br'.split()],
|
||||||
|
|
||||||
'╠': [p(inner_corner, which='tr'), p(inner_corner, which='br'), p(dvline, only='left')],
|
'╠': [p(inner_corner, which='tr'), p(inner_corner, which='br'), p(dvline, only='left')],
|
||||||
|
|
||||||
'╣': [p(inner_corner, which='tl'), p(inner_corner, which='bl'), p(dvline, only='right')],
|
'╣': [p(inner_corner, which='tl'), p(inner_corner, which='bl'), p(dvline, only='right')],
|
||||||
|
|
||||||
'╦': [p(inner_corner, which='bl'), p(inner_corner, which='br'), p(dhline, only='top')],
|
'╦': [p(inner_corner, which='bl'), p(inner_corner, which='br'), p(dhline, only='top')],
|
||||||
|
|
||||||
'╩': [p(inner_corner, which='tl'), p(inner_corner, which='tr'), p(dhline, only='bottom')],
|
'╩': [p(inner_corner, which='tl'), p(inner_corner, which='tr'), p(dhline, only='bottom')],
|
||||||
|
|
||||||
'╱': [p(cross_line, left=False)],
|
'╱': [p(cross_line, left=False)],
|
||||||
'╲': [cross_line],
|
'╲': [cross_line],
|
||||||
'╳': [cross_line, p(cross_line, left=False)],
|
'╳': [cross_line, p(cross_line, left=False)],
|
||||||
'▀': [p(vblock, frac=1/2)],
|
'▀': [p(eight_block, horizontal=True, which=(0, 1, 2, 3))],
|
||||||
'▁': [p(vblock, frac=1/8, gravity='bottom')],
|
'▁': [p(eight_bar, which=7, horizontal=True)],
|
||||||
'▂': [p(vblock, frac=1/4, gravity='bottom')],
|
'▂': [p(eight_block, horizontal=True, which=(6, 7))],
|
||||||
'▃': [p(vblock, frac=3/8, gravity='bottom')],
|
'▃': [p(eight_block, horizontal=True, which=(5, 6, 7))],
|
||||||
'▄': [p(vblock, frac=1/2, gravity='bottom')],
|
'▄': [p(eight_block, horizontal=True, which=(4, 5, 6, 7))],
|
||||||
'▅': [p(vblock, frac=5/8, gravity='bottom')],
|
'▅': [p(eight_block, horizontal=True, which=(3, 4, 5, 6, 7))],
|
||||||
'▆': [p(vblock, frac=3/4, gravity='bottom')],
|
'▆': [p(eight_block, horizontal=True, which=(2, 3, 4, 5, 6, 7))],
|
||||||
'▇': [p(vblock, frac=7/8, gravity='bottom')],
|
'▇': [p(eight_block, horizontal=True, which=(1, 2, 3, 4, 5, 6, 7))],
|
||||||
'█': [p(vblock, frac=1, gravity='bottom')],
|
'█': [p(eight_block, horizontal=True, which=(0, 1, 2, 3, 4, 5, 6, 7))],
|
||||||
'▉': [p(hblock, frac=7/8)],
|
'▉': [p(eight_block, which=(0, 1, 2, 3, 4, 5, 6))],
|
||||||
'▊': [p(hblock, frac=3/4)],
|
'▊': [p(eight_block, which=(0, 1, 2, 3, 4, 5))],
|
||||||
'▋': [p(hblock, frac=5/8)],
|
'▋': [p(eight_block, which=(0, 1, 2, 3, 4))],
|
||||||
'▌': [p(hblock, frac=1/2)],
|
'▌': [p(eight_block, which=(0, 1, 2, 3))],
|
||||||
'▍': [p(hblock, frac=3/8)],
|
'▍': [p(eight_block, which=(0, 1, 2))],
|
||||||
'▎': [p(hblock, frac=1/4)],
|
'▎': [p(eight_block, which=(0, 1))],
|
||||||
'▏': [p(hblock, frac=1/8)],
|
'▏': [p(eight_bar)],
|
||||||
'▐': [p(hblock, frac=1/2, gravity='right')],
|
'▐': [p(eight_block, which=(4, 5, 6, 7))],
|
||||||
'░': [p(shade, light=True)],
|
'░': [p(shade, light=True)],
|
||||||
'▒': [shade],
|
'▒': [shade],
|
||||||
'▓': [p(shade, invert=True)],
|
'▓': [p(shade, invert=True)],
|
||||||
'▔': [p(vblock, frac=1/8)],
|
'▔': [p(eight_bar, horizontal=True)],
|
||||||
'▕': [p(hblock, frac=1/8, gravity='right')],
|
'▕': [p(eight_bar, which=7)],
|
||||||
'▖': [p(quad, y=1)],
|
'▖': [p(quad, y=1)],
|
||||||
'▗': [p(quad, x=1, y=1)],
|
'▗': [p(quad, x=1, y=1)],
|
||||||
'▘': [quad],
|
'▘': [quad],
|
||||||
@@ -637,14 +784,110 @@ box_chars: Dict[str, List[Callable]] = {
|
|||||||
'▝': [p(quad, x=1)],
|
'▝': [p(quad, x=1)],
|
||||||
'▞': [p(quad, x=1), p(quad, y=1)],
|
'▞': [p(quad, x=1), p(quad, y=1)],
|
||||||
'▟': [p(quad, x=1), p(quad, y=1), p(quad, x=1, y=1)],
|
'▟': [p(quad, x=1), p(quad, y=1), p(quad, x=1, y=1)],
|
||||||
|
|
||||||
|
'🬼': [p(smooth_mosaic, a=(0, 0.75), b=(0.5, 1))],
|
||||||
|
'🬽': [p(smooth_mosaic, a=(0, 0.75), b=(1, 1))],
|
||||||
|
'🬾': [p(smooth_mosaic, a=(0, 0.5), b=(0.5, 1))],
|
||||||
|
'🬿': [p(smooth_mosaic, a=(0, 0.5), b=(1, 1))],
|
||||||
|
'🭀': [p(smooth_mosaic, a=(0, 0), b=(0.5, 1))],
|
||||||
|
|
||||||
|
'🭁': [p(smooth_mosaic, a=(0, 0.25), b=(0.5, 0))],
|
||||||
|
'🭂': [p(smooth_mosaic, a=(0, 0.25), b=(1, 0))],
|
||||||
|
'🭃': [p(smooth_mosaic, a=(0, 0.75), b=(0.5, 0))],
|
||||||
|
'🭄': [p(smooth_mosaic, a=(0, 0.75), b=(1, 0))],
|
||||||
|
'🭅': [p(smooth_mosaic, a=(0, 1), b=(0.5, 0))],
|
||||||
|
'🭆': [p(smooth_mosaic, a=(0, 0.75), b=(1, 0.25))],
|
||||||
|
|
||||||
|
'🭇': [p(smooth_mosaic, a=(0.5, 1), b=(1, 0.75))],
|
||||||
|
'🭈': [p(smooth_mosaic, a=(0, 1), b=(1, 0.75))],
|
||||||
|
'🭉': [p(smooth_mosaic, a=(0.5, 1), b=(1, 0.25))],
|
||||||
|
'🭊': [p(smooth_mosaic, a=(0, 1), b=(1, 0.25))],
|
||||||
|
'🭋': [p(smooth_mosaic, a=(0.5, 1), b=(1, 0))],
|
||||||
|
|
||||||
|
'🭌': [p(smooth_mosaic, a=(0.5, 0), b=(1, 0.25))],
|
||||||
|
'🭍': [p(smooth_mosaic, a=(0, 0), b=(1, 0.25))],
|
||||||
|
'🭎': [p(smooth_mosaic, a=(0.5, 0), b=(1, 0.75))],
|
||||||
|
'🭏': [p(smooth_mosaic, a=(0, 0), b=(1, 0.75))],
|
||||||
|
'🭐': [p(smooth_mosaic, a=(0.5, 0), b=(1, 1))],
|
||||||
|
'🭑': [p(smooth_mosaic, a=(0, 0.25), b=(1, 0.75))],
|
||||||
|
|
||||||
|
'🭒': [p(smooth_mosaic, lower=False, a=(0, 0.75), b=(0.5, 1))],
|
||||||
|
'🭓': [p(smooth_mosaic, lower=False, a=(0, 0.75), b=(1, 1))],
|
||||||
|
'🭔': [p(smooth_mosaic, lower=False, a=(0, 0.25), b=(0.5, 1))],
|
||||||
|
'🭕': [p(smooth_mosaic, lower=False, a=(0, 0.25), b=(1, 1))],
|
||||||
|
'🭖': [p(smooth_mosaic, lower=False, a=(0, 0), b=(0.5, 1))],
|
||||||
|
|
||||||
|
'🭗': [p(smooth_mosaic, lower=False, a=(0, 0.25), b=(0.5, 0))],
|
||||||
|
'🭘': [p(smooth_mosaic, lower=False, a=(0, 0.25), b=(1, 0))],
|
||||||
|
'🭙': [p(smooth_mosaic, lower=False, a=(0, 0.75), b=(0.5, 0))],
|
||||||
|
'🭚': [p(smooth_mosaic, lower=False, a=(0, 0.75), b=(1, 0))],
|
||||||
|
'🭛': [p(smooth_mosaic, lower=False, a=(0, 1), b=(0.5, 0))],
|
||||||
|
|
||||||
|
'🭜': [p(smooth_mosaic, lower=False, a=(0, 0.75), b=(1, 0.25))],
|
||||||
|
'🭝': [p(smooth_mosaic, lower=False, a=(0.5, 1), b=(1, 0.75))],
|
||||||
|
'🭞': [p(smooth_mosaic, lower=False, a=(0, 1), b=(1, 0.75))],
|
||||||
|
'🭟': [p(smooth_mosaic, lower=False, a=(0.5, 1), b=(1, 0.25))],
|
||||||
|
'🭠': [p(smooth_mosaic, lower=False, a=(0, 1), b=(1, 0.25))],
|
||||||
|
'🭡': [p(smooth_mosaic, lower=False, a=(0.5, 1), b=(1, 0))],
|
||||||
|
|
||||||
|
'🭢': [p(smooth_mosaic, lower=False, a=(0.5, 0), b=(1, 0.25))],
|
||||||
|
'🭣': [p(smooth_mosaic, lower=False, a=(0, 0), b=(1, 0.25))],
|
||||||
|
'🭤': [p(smooth_mosaic, lower=False, a=(0.5, 0), b=(1, 0.75))],
|
||||||
|
'🭥': [p(smooth_mosaic, lower=False, a=(0, 0), b=(1, 0.75))],
|
||||||
|
'🭦': [p(smooth_mosaic, lower=False, a=(0.5, 0), b=(1, 1))],
|
||||||
|
'🭧': [p(smooth_mosaic, lower=False, a=(0, 0.25), b=(1, 0.75))],
|
||||||
|
|
||||||
|
'🭨': [p(half_triangle, inverted=True)],
|
||||||
|
'🭩': [p(half_triangle, which='top', inverted=True)],
|
||||||
|
'🭪': [p(half_triangle, which='right', inverted=True)],
|
||||||
|
'🭫': [p(half_triangle, which='bottom', inverted=True)],
|
||||||
|
'🭬': [half_triangle],
|
||||||
|
'🭭': [p(half_triangle, which='top')],
|
||||||
|
'🭮': [p(half_triangle, which='right')],
|
||||||
|
'🭯': [p(half_triangle, which='bottom')],
|
||||||
|
|
||||||
|
'🭼': [eight_bar, p(eight_bar, which=7, horizontal=True)],
|
||||||
|
'🭽': [eight_bar, p(eight_bar, horizontal=True)],
|
||||||
|
'🭾': [p(eight_bar, which=7), p(eight_bar, horizontal=True)],
|
||||||
|
'🭿': [p(eight_bar, which=7), p(eight_bar, which=7, horizontal=True)],
|
||||||
|
'🮀': [p(eight_bar, horizontal=True), p(eight_bar, which=7, horizontal=True)],
|
||||||
|
'🮁': [
|
||||||
|
p(eight_bar, horizontal=True), p(eight_bar, which=2, horizontal=True),
|
||||||
|
p(eight_bar, which=4, horizontal=True), p(eight_bar, which=7, horizontal=True)],
|
||||||
|
'🮂': [p(eight_block, horizontal=True, which=(0, 1))],
|
||||||
|
'🮃': [p(eight_block, horizontal=True, which=(0, 1, 2))],
|
||||||
|
'🮄': [p(eight_block, horizontal=True, which=(0, 1, 2, 3, 4))],
|
||||||
|
'🮅': [p(eight_block, horizontal=True, which=(0, 1, 2, 3, 4, 5))],
|
||||||
|
'🮆': [p(eight_block, horizontal=True, which=(0, 1, 2, 3, 4, 5, 6))],
|
||||||
|
'🮇': [p(eight_block, which=(6, 7))],
|
||||||
|
'🮈': [p(eight_block, which=(5, 6, 7))],
|
||||||
|
'🮉': [p(eight_block, which=(3, 4, 5, 6, 7))],
|
||||||
|
'🮊': [p(eight_block, which=(2, 3, 4, 5, 6, 7))],
|
||||||
|
'🮋': [p(eight_block, which=(1, 2, 3, 4, 5, 6, 7))],
|
||||||
|
|
||||||
|
'🮠': [mid_lines],
|
||||||
|
'🮡': [p(mid_lines, pts=('tr',))],
|
||||||
|
'🮢': [p(mid_lines, pts=('lb',))],
|
||||||
|
'🮣': [p(mid_lines, pts=('br',))],
|
||||||
|
'🮤': [p(mid_lines, pts=('lt', 'lb'))],
|
||||||
|
'🮥': [p(mid_lines, pts=('rt', 'rb'))],
|
||||||
|
'🮦': [p(mid_lines, pts=('rb', 'lb'))],
|
||||||
|
'🮧': [p(mid_lines, pts=('rt', 'lt'))],
|
||||||
|
'🮨': [p(mid_lines, pts=('rb', 'lt'))],
|
||||||
|
'🮩': [p(mid_lines, pts=('lb', 'rt'))],
|
||||||
|
'🮪': [p(mid_lines, pts=('lb', 'rt', 'rb'))],
|
||||||
|
'🮫': [p(mid_lines, pts=('lb', 'lt', 'rb'))],
|
||||||
|
'🮬': [p(mid_lines, pts=('rt', 'lt', 'rb'))],
|
||||||
|
'🮭': [p(mid_lines, pts=('rt', 'lt', 'lb'))],
|
||||||
|
'🮮': [p(mid_lines, pts=('rt', 'rb', 'lt', 'lb'))],
|
||||||
}
|
}
|
||||||
|
|
||||||
t, f = 1, 3
|
t, f = 1, 3
|
||||||
for start in '┌┐└┘':
|
for start in '┌┐└┘':
|
||||||
for i, (hlevel, vlevel) in enumerate(((t, t), (f, t), (t, f), (f, f))):
|
for i, (hlevel, vlevel) in enumerate(((t, t), (f, t), (t, f), (f, f))):
|
||||||
box_chars[chr(ord(start) + i)] = [p(corner, which=start, hlevel=hlevel, vlevel=vlevel)]
|
box_chars[chr(ord(start) + i)] = [p(corner, which=start, hlevel=hlevel, vlevel=vlevel)]
|
||||||
for ch, c in zip('╭╮╯╰', '┌┐┘└'):
|
for ch in '╭╮╰╯':
|
||||||
box_chars[ch] = [p(corner, which=c)] # TODO: Make these rounded
|
box_chars[ch] = [p(rounded_corner, which=ch)]
|
||||||
|
|
||||||
for i, (a_, b_, c_, d_) in enumerate((
|
for i, (a_, b_, c_, d_) in enumerate((
|
||||||
(t, t, t, t), (f, t, t, t), (t, f, t, t), (f, f, t, t), (t, t, f, t), (t, t, t, f), (t, t, f, f),
|
(t, t, t, t), (f, t, t, t), (t, f, t, t), (f, f, t, t), (t, t, f, t), (t, t, t, f), (t, t, f, f),
|
||||||
@@ -666,6 +909,18 @@ for chars, func_ in (('╒╕╘╛', dvcorner), ('╓╖╙╜', dhcorner), ('
|
|||||||
box_chars[ch] = [p(cast(Callable, func_), which=ch)]
|
box_chars[ch] = [p(cast(Callable, func_), which=ch)]
|
||||||
|
|
||||||
|
|
||||||
|
c = 0x1fb00
|
||||||
|
for i in range(1, 63):
|
||||||
|
if i in (20, 40):
|
||||||
|
continue
|
||||||
|
box_chars[chr(c)] = [p(sextant, which=i)]
|
||||||
|
c += 1
|
||||||
|
|
||||||
|
for i in range(1, 7):
|
||||||
|
box_chars[chr(0x1fb6f + i)] = [p(eight_bar, which=i)]
|
||||||
|
box_chars[chr(0x1fb75 + i)] = [p(eight_bar, which=i, horizontal=True)]
|
||||||
|
|
||||||
|
|
||||||
def render_box_char(ch: str, buf: BufType, width: int, height: int, dpi: float = 96.0) -> BufType:
|
def render_box_char(ch: str, buf: BufType, width: int, height: int, dpi: float = 96.0) -> BufType:
|
||||||
global _dpi
|
global _dpi
|
||||||
_dpi = dpi
|
_dpi = dpi
|
||||||
|
|||||||
@@ -84,9 +84,14 @@ def find_best_match(family: str, bold: bool = False, italic: bool = False, monos
|
|||||||
# First look for an exact match
|
# First look for an exact match
|
||||||
for selector in ('ps_map', 'full_map', 'family_map'):
|
for selector in ('ps_map', 'full_map', 'family_map'):
|
||||||
candidates = font_map[selector].get(q)
|
candidates = font_map[selector].get(q)
|
||||||
if candidates:
|
if not candidates:
|
||||||
candidates.sort(key=score)
|
continue
|
||||||
return candidates[0]
|
if len(candidates) == 1 and (bold or italic) and candidates[0].get('family') == candidates[0].get('full_name'):
|
||||||
|
# IBM Plex Mono does this, where the full name of the regular font
|
||||||
|
# face is the same as its family name
|
||||||
|
continue
|
||||||
|
candidates.sort(key=score)
|
||||||
|
return candidates[0]
|
||||||
|
|
||||||
# Use fc-match to see if we can find a monospaced font that matches family
|
# Use fc-match to see if we can find a monospaced font that matches family
|
||||||
for spacing in (FC_MONO, FC_DUAL):
|
for spacing in (FC_MONO, FC_DUAL):
|
||||||
|
|||||||
@@ -457,7 +457,7 @@ downsample_bitmap(ProcessedBitmap *bm, unsigned int width, unsigned int cell_hei
|
|||||||
// it matters
|
// it matters
|
||||||
float ratio = MAX((float)bm->width / width, (float)bm->rows / cell_height);
|
float ratio = MAX((float)bm->width / width, (float)bm->rows / cell_height);
|
||||||
int factor = (int)ceilf(ratio);
|
int factor = (int)ceilf(ratio);
|
||||||
uint8_t *dest = calloc(4, width * cell_height);
|
uint8_t *dest = calloc(4, (size_t)width * cell_height);
|
||||||
if (dest == NULL) fatal("Out of memory");
|
if (dest == NULL) fatal("Out of memory");
|
||||||
uint8_t *d = dest;
|
uint8_t *d = dest;
|
||||||
|
|
||||||
@@ -497,7 +497,8 @@ static inline bool
|
|||||||
render_color_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline) {
|
render_color_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline) {
|
||||||
(void)baseline;
|
(void)baseline;
|
||||||
unsigned short best = 0, diff = USHRT_MAX;
|
unsigned short best = 0, diff = USHRT_MAX;
|
||||||
for (short i = 0; i < self->face->num_fixed_sizes; i++) {
|
const short limit = self->face->num_fixed_sizes;
|
||||||
|
for (short i = 0; i < limit; i++) {
|
||||||
unsigned short w = self->face->available_sizes[i].width;
|
unsigned short w = self->face->available_sizes[i].width;
|
||||||
unsigned short d = w > (unsigned short)cell_width ? w - (unsigned short)cell_width : (unsigned short)cell_width - w;
|
unsigned short d = w > (unsigned short)cell_width ? w - (unsigned short)cell_width : (unsigned short)cell_width - w;
|
||||||
if (d < diff) {
|
if (d < diff) {
|
||||||
@@ -588,10 +589,16 @@ render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *inf
|
|||||||
if (!render_color_bitmap(self, info[i].codepoint, &bm, cell_width, cell_height, num_cells, baseline)) {
|
if (!render_color_bitmap(self, info[i].codepoint, &bm, cell_width, cell_height, num_cells, baseline)) {
|
||||||
if (PyErr_Occurred()) PyErr_Print();
|
if (PyErr_Occurred()) PyErr_Print();
|
||||||
*was_colored = false;
|
*was_colored = false;
|
||||||
if (!render_bitmap(self, info[i].codepoint, &bm, cell_width, cell_height, num_cells, bold, italic, true, fg)) return false;
|
if (!render_bitmap(self, info[i].codepoint, &bm, cell_width, cell_height, num_cells, bold, italic, true, fg)) {
|
||||||
|
free_processed_bitmap(&bm);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!render_bitmap(self, info[i].codepoint, &bm, cell_width, cell_height, num_cells, bold, italic, true, fg)) return false;
|
if (!render_bitmap(self, info[i].codepoint, &bm, cell_width, cell_height, num_cells, bold, italic, true, fg)) {
|
||||||
|
free_processed_bitmap(&bm);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
x_offset = x + (float)positions[i].x_offset / 64.0f;
|
x_offset = x + (float)positions[i].x_offset / 64.0f;
|
||||||
y = (float)positions[i].y_offset / 64.0f;
|
y = (float)positions[i].y_offset / 64.0f;
|
||||||
|
|||||||
43
kitty/glfw-wrapper.h
generated
43
kitty/glfw-wrapper.h
generated
@@ -569,7 +569,7 @@
|
|||||||
* @analysis Application programmer error. Fix the offending call.
|
* @analysis Application programmer error. Fix the offending call.
|
||||||
*/
|
*/
|
||||||
#define GLFW_NO_WINDOW_CONTEXT 0x0001000A
|
#define GLFW_NO_WINDOW_CONTEXT 0x0001000A
|
||||||
/*! @brief The reuqested feature is not provided by the platform.
|
/*! @brief The requested feature is not provided by the platform.
|
||||||
*
|
*
|
||||||
* The requested feature is not provided by the platform, so GLFW is unable to
|
* The requested feature is not provided by the platform, so GLFW is unable to
|
||||||
* implement it. The documentation for each function notes if it could emit
|
* implement it. The documentation for each function notes if it could emit
|
||||||
@@ -670,11 +670,19 @@
|
|||||||
* [window attribute](@ref GLFW_FOCUS_ON_SHOW_attrib).
|
* [window attribute](@ref GLFW_FOCUS_ON_SHOW_attrib).
|
||||||
*/
|
*/
|
||||||
#define GLFW_FOCUS_ON_SHOW 0x0002000C
|
#define GLFW_FOCUS_ON_SHOW 0x0002000C
|
||||||
|
|
||||||
|
/*! @brief Mouse input transparency window hint and attribute
|
||||||
|
*
|
||||||
|
* Mouse input transparency [window hint](@ref GLFW_MOUSE_PASSTHROUGH_hint) or
|
||||||
|
* [window attribute](@ref GLFW_MOUSE_PASSTHROUGH_attrib).
|
||||||
|
*/
|
||||||
|
#define GLFW_MOUSE_PASSTHROUGH 0x0002000D
|
||||||
|
|
||||||
/*! @brief Occlusion window attribute
|
/*! @brief Occlusion window attribute
|
||||||
*
|
*
|
||||||
* Occlusion [window attribute](@ref GLFW_OCCLUDED_attrib).
|
* Occlusion [window attribute](@ref GLFW_OCCLUDED_attrib).
|
||||||
*/
|
*/
|
||||||
#define GLFW_OCCLUDED 0x0002000D
|
#define GLFW_OCCLUDED 0x0002000E
|
||||||
/*! @brief Framebuffer bit depth hint.
|
/*! @brief Framebuffer bit depth hint.
|
||||||
*
|
*
|
||||||
* Framebuffer bit depth [hint](@ref GLFW_RED_BITS).
|
* Framebuffer bit depth [hint](@ref GLFW_RED_BITS).
|
||||||
@@ -792,12 +800,17 @@
|
|||||||
* and [attribute](@ref GLFW_OPENGL_FORWARD_COMPAT_attrib).
|
* and [attribute](@ref GLFW_OPENGL_FORWARD_COMPAT_attrib).
|
||||||
*/
|
*/
|
||||||
#define GLFW_OPENGL_FORWARD_COMPAT 0x00022006
|
#define GLFW_OPENGL_FORWARD_COMPAT 0x00022006
|
||||||
/*! @brief OpenGL debug context hint and attribute.
|
/*! @brief Debug mode context hint and attribute.
|
||||||
*
|
*
|
||||||
* OpenGL debug context [hint](@ref GLFW_OPENGL_DEBUG_CONTEXT_hint) and
|
* Debug mode context [hint](@ref GLFW_CONTEXT_DEBUG_hint) and
|
||||||
* [attribute](@ref GLFW_OPENGL_DEBUG_CONTEXT_attrib).
|
* [attribute](@ref GLFW_CONTEXT_DEBUG_attrib).
|
||||||
*/
|
*/
|
||||||
#define GLFW_OPENGL_DEBUG_CONTEXT 0x00022007
|
#define GLFW_CONTEXT_DEBUG 0x00022007
|
||||||
|
/*! @brief Legacy name for compatibility.
|
||||||
|
*
|
||||||
|
* This is an alias for compatibility with earlier versions.
|
||||||
|
*/
|
||||||
|
#define GLFW_OPENGL_DEBUG_CONTEXT GLFW_CONTEXT_DEBUG
|
||||||
/*! @brief OpenGL profile hint and attribute.
|
/*! @brief OpenGL profile hint and attribute.
|
||||||
*
|
*
|
||||||
* OpenGL profile [hint](@ref GLFW_OPENGL_PROFILE_hint) and
|
* OpenGL profile [hint](@ref GLFW_OPENGL_PROFILE_hint) and
|
||||||
@@ -880,6 +893,14 @@
|
|||||||
#define GLFW_EGL_CONTEXT_API 0x00036002
|
#define GLFW_EGL_CONTEXT_API 0x00036002
|
||||||
#define GLFW_OSMESA_CONTEXT_API 0x00036003
|
#define GLFW_OSMESA_CONTEXT_API 0x00036003
|
||||||
|
|
||||||
|
#define GLFW_ANGLE_PLATFORM_TYPE_NONE 0x00037001
|
||||||
|
#define GLFW_ANGLE_PLATFORM_TYPE_OPENGL 0x00037002
|
||||||
|
#define GLFW_ANGLE_PLATFORM_TYPE_OPENGLES 0x00037003
|
||||||
|
#define GLFW_ANGLE_PLATFORM_TYPE_D3D9 0x00037004
|
||||||
|
#define GLFW_ANGLE_PLATFORM_TYPE_D3D11 0x00037005
|
||||||
|
#define GLFW_ANGLE_PLATFORM_TYPE_VULKAN 0x00037007
|
||||||
|
#define GLFW_ANGLE_PLATFORM_TYPE_METAL 0x00037008
|
||||||
|
|
||||||
/*! @defgroup shapes Standard cursor shapes
|
/*! @defgroup shapes Standard cursor shapes
|
||||||
* @brief Standard system cursor shapes.
|
* @brief Standard system cursor shapes.
|
||||||
*
|
*
|
||||||
@@ -913,8 +934,12 @@ typedef enum {
|
|||||||
* Joystick hat buttons [init hint](@ref GLFW_JOYSTICK_HAT_BUTTONS).
|
* Joystick hat buttons [init hint](@ref GLFW_JOYSTICK_HAT_BUTTONS).
|
||||||
*/
|
*/
|
||||||
#define GLFW_JOYSTICK_HAT_BUTTONS 0x00050001
|
#define GLFW_JOYSTICK_HAT_BUTTONS 0x00050001
|
||||||
#define GLFW_DEBUG_KEYBOARD 0x00050002
|
/*! @brief ANGLE rendering backend init hint.
|
||||||
#define GLFW_ENABLE_JOYSTICKS 0x00050003
|
*
|
||||||
|
* ANGLE rendering backend [init hint](@ref GLFW_ANGLE_PLATFORM_TYPE_hint).
|
||||||
|
*/
|
||||||
|
#define GLFW_ANGLE_PLATFORM_TYPE 0x00050002
|
||||||
|
#define GLFW_DEBUG_KEYBOARD 0x00050003
|
||||||
/*! @brief macOS specific init hint.
|
/*! @brief macOS specific init hint.
|
||||||
*
|
*
|
||||||
* macOS specific [init hint](@ref GLFW_COCOA_CHDIR_RESOURCES_hint).
|
* macOS specific [init hint](@ref GLFW_COCOA_CHDIR_RESOURCES_hint).
|
||||||
@@ -1215,7 +1240,7 @@ typedef void (* GLFWwindowiconifyfun)(GLFWwindow*,int);
|
|||||||
* @endcode
|
* @endcode
|
||||||
*
|
*
|
||||||
* @param[in] window The window that was maximized or restored.
|
* @param[in] window The window that was maximized or restored.
|
||||||
* @param[in] iconified `true` if the window was maximized, or
|
* @param[in] maximized `true` if the window was maximized, or
|
||||||
* `false` if it was restored.
|
* `false` if it was restored.
|
||||||
*
|
*
|
||||||
* @sa @ref window_maximize
|
* @sa @ref window_maximize
|
||||||
|
|||||||
63
kitty/glfw.c
63
kitty/glfw.c
@@ -11,9 +11,11 @@
|
|||||||
#include "glfw-wrapper.h"
|
#include "glfw-wrapper.h"
|
||||||
extern bool cocoa_make_window_resizable(void *w, bool);
|
extern bool cocoa_make_window_resizable(void *w, bool);
|
||||||
extern void cocoa_focus_window(void *w);
|
extern void cocoa_focus_window(void *w);
|
||||||
|
extern long cocoa_window_number(void *w);
|
||||||
extern void cocoa_create_global_menu(void);
|
extern void cocoa_create_global_menu(void);
|
||||||
extern void cocoa_hide_window_title(void *w);
|
extern void cocoa_hide_window_title(void *w);
|
||||||
extern void cocoa_hide_titlebar(void *w);
|
extern void cocoa_hide_titlebar(void *w);
|
||||||
|
extern void cocoa_system_beep(void);
|
||||||
extern void cocoa_set_activation_policy(bool);
|
extern void cocoa_set_activation_policy(bool);
|
||||||
extern void cocoa_set_titlebar_color(void *w, color_type color);
|
extern void cocoa_set_titlebar_color(void *w, color_type color);
|
||||||
extern bool cocoa_alt_option_key_pressed(unsigned long);
|
extern bool cocoa_alt_option_key_pressed(unsigned long);
|
||||||
@@ -364,6 +366,7 @@ static void
|
|||||||
application_close_requested_callback(int flags) {
|
application_close_requested_callback(int flags) {
|
||||||
if (flags) {
|
if (flags) {
|
||||||
global_state.quit_request = IMPERATIVE_CLOSE_REQUESTED;
|
global_state.quit_request = IMPERATIVE_CLOSE_REQUESTED;
|
||||||
|
global_state.has_pending_closes = true;
|
||||||
request_tick_callback();
|
request_tick_callback();
|
||||||
} else {
|
} else {
|
||||||
if (global_state.quit_request == NO_CLOSE_REQUESTED) {
|
if (global_state.quit_request == NO_CLOSE_REQUESTED) {
|
||||||
@@ -589,7 +592,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
|||||||
#endif
|
#endif
|
||||||
if (!global_state.is_wayland) {
|
if (!global_state.is_wayland) {
|
||||||
// On Wayland windows dont get a content scale until they receive an enterEvent anyway
|
// On Wayland windows dont get a content scale until they receive an enterEvent anyway
|
||||||
// which wont happen until the event loop ticks, so using a temp window is useless.
|
// which won't happen until the event loop ticks, so using a temp window is useless.
|
||||||
temp_window = glfwCreateWindow(640, 480, "temp", NULL, common_context);
|
temp_window = glfwCreateWindow(640, 480, "temp", NULL, common_context);
|
||||||
if (temp_window == NULL) { fatal("Failed to create GLFW temp window! This usually happens because of old/broken OpenGL drivers. kitty requires working OpenGL 3.3 drivers."); }
|
if (temp_window == NULL) { fatal("Failed to create GLFW temp window! This usually happens because of old/broken OpenGL drivers. kitty requires working OpenGL 3.3 drivers."); }
|
||||||
}
|
}
|
||||||
@@ -813,9 +816,6 @@ glfw_init(PyObject UNUSED *self, PyObject *args) {
|
|||||||
if (err) { PyErr_SetString(PyExc_RuntimeError, err); return NULL; }
|
if (err) { PyErr_SetString(PyExc_RuntimeError, err); return NULL; }
|
||||||
glfwSetErrorCallback(error_callback);
|
glfwSetErrorCallback(error_callback);
|
||||||
glfwInitHint(GLFW_DEBUG_KEYBOARD, debug_keyboard);
|
glfwInitHint(GLFW_DEBUG_KEYBOARD, debug_keyboard);
|
||||||
// Joysticks cause slow startup on some linux systems, see
|
|
||||||
// https://github.com/kovidgoyal/kitty/issues/830
|
|
||||||
glfwInitHint(GLFW_ENABLE_JOYSTICKS, 0);
|
|
||||||
OPT(debug_keyboard) = debug_keyboard != 0;
|
OPT(debug_keyboard) = debug_keyboard != 0;
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
glfwInitHint(GLFW_COCOA_CHDIR_RESOURCES, 0);
|
glfwInitHint(GLFW_COCOA_CHDIR_RESOURCES, 0);
|
||||||
@@ -889,16 +889,14 @@ get_clipboard_string(PYNOARG) {
|
|||||||
return Py_BuildValue("s", "");
|
return Py_BuildValue("s", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
static void
|
||||||
ring_audio_bell(OSWindow *w UNUSED) {
|
ring_audio_bell(void) {
|
||||||
static monotonic_t last_bell_at = -1;
|
static monotonic_t last_bell_at = -1;
|
||||||
monotonic_t now = monotonic();
|
monotonic_t now = monotonic();
|
||||||
if (now - last_bell_at <= ms_to_monotonic_t(100ll)) return;
|
if (last_bell_at >= 0 && now - last_bell_at <= ms_to_monotonic_t(100ll)) return;
|
||||||
last_bell_at = now;
|
last_bell_at = now;
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
if (w->handle) {
|
cocoa_system_beep();
|
||||||
glfwWindowBell(w->handle);
|
|
||||||
}
|
|
||||||
#else
|
#else
|
||||||
play_canberra_sound("bell", "kitty bell");
|
play_canberra_sound("bell", "kitty bell");
|
||||||
#endif
|
#endif
|
||||||
@@ -906,8 +904,7 @@ ring_audio_bell(OSWindow *w UNUSED) {
|
|||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
ring_bell(PYNOARG) {
|
ring_bell(PYNOARG) {
|
||||||
OSWindow *w = current_os_window();
|
ring_audio_bell();
|
||||||
ring_audio_bell(w);
|
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -961,7 +958,7 @@ void
|
|||||||
request_window_attention(id_type kitty_window_id, bool audio_bell) {
|
request_window_attention(id_type kitty_window_id, bool audio_bell) {
|
||||||
OSWindow *w = os_window_for_kitty_window(kitty_window_id);
|
OSWindow *w = os_window_for_kitty_window(kitty_window_id);
|
||||||
if (w) {
|
if (w) {
|
||||||
if (audio_bell) ring_audio_bell(w);
|
if (audio_bell) ring_audio_bell();
|
||||||
if (OPT(window_alert_on_bell)) glfwRequestWindowAttention(w->handle);
|
if (OPT(window_alert_on_bell)) glfwRequestWindowAttention(w->handle);
|
||||||
glfwPostEmptyEvent();
|
glfwPostEmptyEvent();
|
||||||
}
|
}
|
||||||
@@ -1025,18 +1022,35 @@ x11_display(PYNOARG) {
|
|||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static OSWindow*
|
||||||
|
find_os_window(PyObject *os_wid) {
|
||||||
|
id_type os_window_id = PyLong_AsUnsignedLongLong(os_wid);
|
||||||
|
for (size_t i = 0; i < global_state.num_os_windows; i++) {
|
||||||
|
OSWindow *w = global_state.os_windows + i;
|
||||||
|
if (w->id == os_window_id) return w;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
x11_window_id(PyObject UNUSED *self, PyObject *os_wid) {
|
x11_window_id(PyObject UNUSED *self, PyObject *os_wid) {
|
||||||
if (glfwGetX11Window) {
|
OSWindow *w = find_os_window(os_wid);
|
||||||
id_type os_window_id = PyLong_AsUnsignedLongLong(os_wid);
|
if (!w) { PyErr_SetString(PyExc_ValueError, "No OSWindow with the specified id found"); return NULL; }
|
||||||
for (size_t i = 0; i < global_state.num_os_windows; i++) {
|
if (!glfwGetX11Window) { PyErr_SetString(PyExc_RuntimeError, "Failed to load glfwGetX11Window"); return NULL; }
|
||||||
OSWindow *w = global_state.os_windows + i;
|
return Py_BuildValue("l", (long)glfwGetX11Window(w->handle));
|
||||||
if (w->id == os_window_id) return Py_BuildValue("l", (long)glfwGetX11Window(w->handle));
|
}
|
||||||
}
|
|
||||||
}
|
static PyObject*
|
||||||
else { PyErr_SetString(PyExc_RuntimeError, "Failed to load glfwGetX11Window"); return NULL; }
|
cocoa_window_id(PyObject UNUSED *self, PyObject *os_wid) {
|
||||||
PyErr_SetString(PyExc_ValueError, "No OSWindow with the specified id found");
|
OSWindow *w = find_os_window(os_wid);
|
||||||
|
if (!w) { PyErr_SetString(PyExc_ValueError, "No OSWindow with the specified id found"); return NULL; }
|
||||||
|
if (!glfwGetCocoaWindow) { PyErr_SetString(PyExc_RuntimeError, "Failed to load glfwGetCocoaWindow"); return NULL; }
|
||||||
|
#ifdef __APPLE__
|
||||||
|
return Py_BuildValue("l", (long)cocoa_window_number(glfwGetCocoaWindow(w->handle)));
|
||||||
|
#else
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "cocoa_window_id() is only supported on Mac");
|
||||||
return NULL;
|
return NULL;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
@@ -1078,7 +1092,7 @@ set_custom_cursor(PyObject *self UNUSED, PyObject *args) {
|
|||||||
size_t count = MIN((size_t)PyTuple_GET_SIZE(images), arraysz(gimages));
|
size_t count = MIN((size_t)PyTuple_GET_SIZE(images), arraysz(gimages));
|
||||||
for (size_t i = 0; i < count; i++) {
|
for (size_t i = 0; i < count; i++) {
|
||||||
if (!PyArg_ParseTuple(PyTuple_GET_ITEM(images, i), "s#ii", &gimages[i].pixels, &sz, &gimages[i].width, &gimages[i].height)) return NULL;
|
if (!PyArg_ParseTuple(PyTuple_GET_ITEM(images, i), "s#ii", &gimages[i].pixels, &sz, &gimages[i].width, &gimages[i].height)) return NULL;
|
||||||
if (gimages[i].width * gimages[i].height * 4 != sz) {
|
if ((Py_ssize_t)gimages[i].width * gimages[i].height * 4 != sz) {
|
||||||
PyErr_SetString(PyExc_ValueError, "The image data size does not match its width and height");
|
PyErr_SetString(PyExc_ValueError, "The image data size does not match its width and height");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -1220,6 +1234,7 @@ static PyMethodDef module_methods[] = {
|
|||||||
#ifndef __APPLE__
|
#ifndef __APPLE__
|
||||||
METHODB(dbus_send_notification, METH_VARARGS),
|
METHODB(dbus_send_notification, METH_VARARGS),
|
||||||
#endif
|
#endif
|
||||||
|
METHODB(cocoa_window_id, METH_O),
|
||||||
{"glfw_init", (PyCFunction)glfw_init, METH_VARARGS, ""},
|
{"glfw_init", (PyCFunction)glfw_init, METH_VARARGS, ""},
|
||||||
{"glfw_terminate", (PyCFunction)glfw_terminate, METH_NOARGS, ""},
|
{"glfw_terminate", (PyCFunction)glfw_terminate, METH_NOARGS, ""},
|
||||||
{"glfw_get_physical_dpi", (PyCFunction)glfw_get_physical_dpi, METH_NOARGS, ""},
|
{"glfw_get_physical_dpi", (PyCFunction)glfw_get_physical_dpi, METH_NOARGS, ""},
|
||||||
@@ -1531,7 +1546,7 @@ init_glfw(PyObject *m) {
|
|||||||
ADDC(GLFW_CONTEXT_REVISION);
|
ADDC(GLFW_CONTEXT_REVISION);
|
||||||
ADDC(GLFW_CONTEXT_ROBUSTNESS);
|
ADDC(GLFW_CONTEXT_ROBUSTNESS);
|
||||||
ADDC(GLFW_OPENGL_FORWARD_COMPAT);
|
ADDC(GLFW_OPENGL_FORWARD_COMPAT);
|
||||||
ADDC(GLFW_OPENGL_DEBUG_CONTEXT);
|
ADDC(GLFW_CONTEXT_DEBUG);
|
||||||
ADDC(GLFW_OPENGL_PROFILE);
|
ADDC(GLFW_OPENGL_PROFILE);
|
||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|||||||
@@ -348,7 +348,7 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
|
|||||||
break;
|
break;
|
||||||
case RGB:
|
case RGB:
|
||||||
case RGBA:
|
case RGBA:
|
||||||
img->load_data.data_sz = g->data_width * g->data_height * (fmt / 8);
|
img->load_data.data_sz = (size_t)g->data_width * g->data_height * (fmt / 8);
|
||||||
if (!img->load_data.data_sz) ABRT(EINVAL, "Zero width/height not allowed");
|
if (!img->load_data.data_sz) ABRT(EINVAL, "Zero width/height not allowed");
|
||||||
img->load_data.is_4byte_aligned = fmt == RGBA || (img->width % 4 == 0);
|
img->load_data.is_4byte_aligned = fmt == RGBA || (img->width % 4 == 0);
|
||||||
img->load_data.is_opaque = fmt == RGB;
|
img->load_data.is_opaque = fmt == RGB;
|
||||||
@@ -404,7 +404,7 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
|
|||||||
else fd = open(fname, O_CLOEXEC | O_RDONLY);
|
else fd = open(fname, O_CLOEXEC | O_RDONLY);
|
||||||
if (fd == -1) ABRT(EBADF, "Failed to open file %s for graphics transmission with error: [%d] %s", fname, errno, strerror(errno));
|
if (fd == -1) ABRT(EBADF, "Failed to open file %s for graphics transmission with error: [%d] %s", fname, errno, strerror(errno));
|
||||||
img->data_loaded = mmap_img_file(self, img, fd, g->data_sz, g->data_offset);
|
img->data_loaded = mmap_img_file(self, img, fd, g->data_sz, g->data_offset);
|
||||||
safe_close(fd);
|
safe_close(fd, __FILE__, __LINE__);
|
||||||
if (tt == 't') {
|
if (tt == 't') {
|
||||||
if (global_state.boss) { call_boss(safe_delete_temp_file, "s", fname); }
|
if (global_state.boss) { call_boss(safe_delete_temp_file, "s", fname); }
|
||||||
else unlink(fname);
|
else unlink(fname);
|
||||||
@@ -461,7 +461,7 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
|
|||||||
} else img->load_data.data = img->load_data.mapped_file;
|
} else img->load_data.data = img->load_data.mapped_file;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
size_t required_sz = (img->load_data.is_opaque ? 3 : 4) * img->width * img->height;
|
size_t required_sz = (size_t)(img->load_data.is_opaque ? 3 : 4) * img->width * img->height;
|
||||||
if (img->load_data.data_sz != required_sz) ABRT(EINVAL, "Image dimensions: %ux%u do not match data size: %zu, expected size: %zu", img->width, img->height, img->load_data.data_sz, required_sz);
|
if (img->load_data.data_sz != required_sz) ABRT(EINVAL, "Image dimensions: %ux%u do not match data size: %zu, expected size: %zu", img->width, img->height, img->load_data.data_sz, required_sz);
|
||||||
if (LIKELY(img->data_loaded && send_to_gpu)) {
|
if (LIKELY(img->data_loaded && send_to_gpu)) {
|
||||||
send_image_to_gpu(&img->texture_id, img->load_data.data, img->width, img->height, img->load_data.is_opaque, img->load_data.is_4byte_aligned, false, REPEAT_CLAMP);
|
send_image_to_gpu(&img->texture_id, img->load_data.data, img->width, img->height, img->load_data.is_opaque, img->load_data.is_4byte_aligned, false, REPEAT_CLAMP);
|
||||||
@@ -645,20 +645,32 @@ grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float scree
|
|||||||
// Image lifetime/scrolling {{{
|
// Image lifetime/scrolling {{{
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
filter_refs(GraphicsManager *self, const void* data, bool free_images, bool (*filter_func)(ImageRef*, Image*, const void*, CellPixelSize), CellPixelSize cell) {
|
filter_refs(GraphicsManager *self, const void* data, bool free_images, bool (*filter_func)(const ImageRef*, Image*, const void*, CellPixelSize), CellPixelSize cell) {
|
||||||
self->layers_dirty = self->image_count > 0;
|
|
||||||
for (size_t i = self->image_count; i-- > 0;) {
|
for (size_t i = self->image_count; i-- > 0;) {
|
||||||
Image *img = self->images + i;
|
Image *img = self->images + i;
|
||||||
for (size_t j = img->refcnt; j-- > 0;) {
|
for (size_t j = img->refcnt; j-- > 0;) {
|
||||||
ImageRef *ref = img->refs + j;
|
ImageRef *ref = img->refs + j;
|
||||||
if (filter_func(ref, img, data, cell)) {
|
if (filter_func(ref, img, data, cell)) {
|
||||||
remove_i_from_array(img->refs, j, img->refcnt);
|
remove_i_from_array(img->refs, j, img->refcnt);
|
||||||
|
self->layers_dirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (img->refcnt == 0 && (free_images || img->client_id == 0)) remove_image(self, i);
|
if (img->refcnt == 0 && (free_images || img->client_id == 0)) remove_image(self, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
modify_refs(GraphicsManager *self, const void* data, bool free_images, bool (*filter_func)(ImageRef*, Image*, const void*, CellPixelSize), CellPixelSize cell) {
|
||||||
|
for (size_t i = self->image_count; i-- > 0;) {
|
||||||
|
Image *img = self->images + i;
|
||||||
|
for (size_t j = img->refcnt; j-- > 0;) {
|
||||||
|
if (filter_func(img->refs + j, img, data, cell)) remove_i_from_array(img->refs, j, img->refcnt);
|
||||||
|
}
|
||||||
|
if (img->refcnt == 0 && (free_images || img->client_id == 0)) remove_image(self, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
scroll_filter_func(ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
|
scroll_filter_func(ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
|
||||||
ScrollData *d = (ScrollData*)data;
|
ScrollData *d = (ScrollData*)data;
|
||||||
@@ -667,12 +679,12 @@ scroll_filter_func(ImageRef *ref, Image UNUSED *img, const void *data, CellPixel
|
|||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
ref_within_region(ImageRef *ref, index_type margin_top, index_type margin_bottom) {
|
ref_within_region(const ImageRef *ref, index_type margin_top, index_type margin_bottom) {
|
||||||
return ref->start_row >= (int32_t)margin_top && ref->start_row + ref->effective_num_rows <= margin_bottom;
|
return ref->start_row >= (int32_t)margin_top && ref->start_row + ref->effective_num_rows <= margin_bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
ref_outside_region(ImageRef *ref, index_type margin_top, index_type margin_bottom) {
|
ref_outside_region(const ImageRef *ref, index_type margin_top, index_type margin_bottom) {
|
||||||
return ref->start_row + ref->effective_num_rows <= margin_top || ref->start_row > (int32_t)margin_bottom;
|
return ref->start_row + ref->effective_num_rows <= margin_top || ref->start_row > (int32_t)margin_bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -709,16 +721,19 @@ scroll_filter_margins_func(ImageRef* ref, Image* img, const void* data, CellPixe
|
|||||||
|
|
||||||
void
|
void
|
||||||
grman_scroll_images(GraphicsManager *self, const ScrollData *data, CellPixelSize cell) {
|
grman_scroll_images(GraphicsManager *self, const ScrollData *data, CellPixelSize cell) {
|
||||||
filter_refs(self, data, true, data->has_margins ? scroll_filter_margins_func : scroll_filter_func, cell);
|
if (self->image_count) {
|
||||||
|
self->layers_dirty = true;
|
||||||
|
modify_refs(self, data, true, data->has_margins ? scroll_filter_margins_func : scroll_filter_func, cell);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
clear_filter_func(ImageRef *ref, Image UNUSED *img, const void UNUSED *data, CellPixelSize cell UNUSED) {
|
clear_filter_func(const ImageRef *ref, Image UNUSED *img, const void UNUSED *data, CellPixelSize cell UNUSED) {
|
||||||
return ref->start_row + (int32_t)ref->effective_num_rows > 0;
|
return ref->start_row + (int32_t)ref->effective_num_rows > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
clear_all_filter_func(ImageRef *ref UNUSED, Image UNUSED *img, const void UNUSED *data, CellPixelSize cell UNUSED) {
|
clear_all_filter_func(const ImageRef *ref UNUSED, Image UNUSED *img, const void UNUSED *data, CellPixelSize cell UNUSED) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -728,37 +743,37 @@ grman_clear(GraphicsManager *self, bool all, CellPixelSize cell) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
id_filter_func(ImageRef UNUSED *ref, Image *img, const void *data, CellPixelSize cell UNUSED) {
|
id_filter_func(const ImageRef UNUSED *ref, Image *img, const void *data, CellPixelSize cell UNUSED) {
|
||||||
uint32_t iid = *(uint32_t*)data;
|
uint32_t iid = *(uint32_t*)data;
|
||||||
return img->client_id == iid;
|
return img->client_id == iid;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
x_filter_func(ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
|
x_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
|
||||||
const GraphicsCommand *g = data;
|
const GraphicsCommand *g = data;
|
||||||
return ref->start_column <= (int32_t)g->x_offset - 1 && ((int32_t)g->x_offset - 1) < ((int32_t)(ref->start_column + ref->effective_num_cols));
|
return ref->start_column <= (int32_t)g->x_offset - 1 && ((int32_t)g->x_offset - 1) < ((int32_t)(ref->start_column + ref->effective_num_cols));
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
y_filter_func(ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
|
y_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
|
||||||
const GraphicsCommand *g = data;
|
const GraphicsCommand *g = data;
|
||||||
return ref->start_row <= (int32_t)g->y_offset - 1 && ((int32_t)(g->y_offset - 1 < ref->start_row + ref->effective_num_rows));
|
return ref->start_row <= (int32_t)g->y_offset - 1 && ((int32_t)(g->y_offset - 1 < ref->start_row + ref->effective_num_rows));
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
z_filter_func(ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
|
z_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
|
||||||
const GraphicsCommand *g = data;
|
const GraphicsCommand *g = data;
|
||||||
return ref->z_index == g->z_index;
|
return ref->z_index == g->z_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
point_filter_func(ImageRef *ref, Image *img, const void *data, CellPixelSize cell) {
|
point_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell) {
|
||||||
return x_filter_func(ref, img, data, cell) && y_filter_func(ref, img, data, cell);
|
return x_filter_func(ref, img, data, cell) && y_filter_func(ref, img, data, cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
point3d_filter_func(ImageRef *ref, Image *img, const void *data, CellPixelSize cell) {
|
point3d_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell) {
|
||||||
return z_filter_func(ref, img, data, cell) && point_filter_func(ref, img, data, cell);
|
return z_filter_func(ref, img, data, cell) && point_filter_func(ref, img, data, cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -891,12 +906,12 @@ W(shm_write) {
|
|||||||
int fd = shm_open(name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
|
int fd = shm_open(name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
|
||||||
if (fd == -1) { PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
|
if (fd == -1) { PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
|
||||||
int ret = ftruncate(fd, sz);
|
int ret = ftruncate(fd, sz);
|
||||||
if (ret != 0) { safe_close(fd); PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
|
if (ret != 0) { safe_close(fd, __FILE__, __LINE__); PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
|
||||||
void *addr = mmap(0, sz, PROT_WRITE, MAP_SHARED, fd, 0);
|
void *addr = mmap(0, sz, PROT_WRITE, MAP_SHARED, fd, 0);
|
||||||
if (addr == MAP_FAILED) { safe_close(fd); PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
|
if (addr == MAP_FAILED) { safe_close(fd, __FILE__, __LINE__); PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
|
||||||
memcpy(addr, data, sz);
|
memcpy(addr, data, sz);
|
||||||
if (munmap(addr, sz) != 0) { safe_close(fd); PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
|
if (munmap(addr, sz) != 0) { safe_close(fd, __FILE__, __LINE__); PyErr_SetFromErrnoWithFilename(PyExc_OSError, name); return NULL; }
|
||||||
safe_close(fd);
|
safe_close(fd, __FILE__, __LINE__);
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user