Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd7b4fcd8e | ||
|
|
f67995d5d3 | ||
|
|
f0e7344bc8 | ||
|
|
aa525c68c7 | ||
|
|
a2e25331a5 | ||
|
|
d8f09d377f | ||
|
|
cc96cb1c75 | ||
|
|
5966f04656 | ||
|
|
dbce9a8f29 | ||
|
|
4333552523 | ||
|
|
c00e945f6e | ||
|
|
56cb628ee8 |
2
.github/workflows/ci.py
vendored
2
.github/workflows/ci.py
vendored
@@ -34,7 +34,7 @@ def install_deps():
|
||||
run('brew', 'install', *items)
|
||||
else:
|
||||
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 ca-certificates'
|
||||
' libxcursor-dev libxcb-xkb-dev libdbus-1-dev libxkbcommon-dev libharfbuzz-dev libx11-xcb-dev'
|
||||
' libpng-dev liblcms2-dev libfontconfig-dev libxkbcommon-x11-dev libcanberra-dev uuid-dev')
|
||||
if is_bundle:
|
||||
|
||||
24
__main__.py
24
__main__.py
@@ -119,21 +119,21 @@ namespaced_entry_points['complete'] = complete
|
||||
|
||||
|
||||
def setup_openssl_environment() -> None:
|
||||
# Workaround for Linux distros that have still failed to get their heads
|
||||
# out of their asses and implement a common location for SSL certificates.
|
||||
# It's not that hard people, there exists a wonderful tool called the symlink
|
||||
# See https://www.mobileread.com/forums/showthread.php?t=256095
|
||||
if 'SSL_CERT_FILE' not in os.environ and 'SSL_CERT_DIR' not in os.environ:
|
||||
if os.access('/etc/pki/tls/certs/ca-bundle.crt', os.R_OK):
|
||||
os.environ['SSL_CERT_FILE'] = '/etc/pki/tls/certs/ca-bundle.crt'
|
||||
setattr(sys, 'kitty_ssl_env_var', 'SSL_CERT_FILE')
|
||||
elif os.path.isdir('/etc/ssl/certs'):
|
||||
os.environ['SSL_CERT_DIR'] = '/etc/ssl/certs'
|
||||
setattr(sys, 'kitty_ssl_env_var', 'SSL_CERT_DIR')
|
||||
# Use our bundled CA certificates instead of the system ones, since
|
||||
# many systems come with no certificates in a useable form or have various
|
||||
# locations for the certificates.
|
||||
d = os.path.dirname
|
||||
ext_dir: str = getattr(sys, 'kitty_extensions_dir')
|
||||
if 'darwin' in sys.platform.lower():
|
||||
cert_file = os.path.join(d(d(d(ext_dir))), 'cacert.pem')
|
||||
else:
|
||||
cert_file = os.path.join(d(ext_dir), 'cacert.pem')
|
||||
os.environ['SSL_CERT_FILE'] = cert_file
|
||||
setattr(sys, 'kitty_ssl_env_var', 'SSL_CERT_FILE')
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if getattr(sys, 'frozen', False) and 'darwin' not in sys.platform.lower():
|
||||
if getattr(sys, 'frozen', False) and getattr(sys, 'kitty_extensions_dir', ''):
|
||||
setup_openssl_environment()
|
||||
first_arg = '' if len(sys.argv) < 2 else sys.argv[1]
|
||||
func = entry_points.get(first_arg)
|
||||
|
||||
@@ -30,6 +30,7 @@ def initialize_constants():
|
||||
kitty_constants['appname'] = re.search(
|
||||
r'appname: str\s+=\s+(u{0,1})[\'"]([^\'"]+)[\'"]', src
|
||||
).group(2)
|
||||
kitty_constants['cacerts_url'] = 'https://curl.haxx.se/ca/cacert.pem'
|
||||
return kitty_constants
|
||||
|
||||
|
||||
|
||||
@@ -92,6 +92,15 @@ def copy_libs(env):
|
||||
subprocess.check_call(['chrpath', '-d', dest])
|
||||
|
||||
|
||||
def add_ca_certs(env):
|
||||
print('Downloading CA certs...')
|
||||
from urllib.request import urlopen
|
||||
cdata = urlopen(kitty_constants['cacerts_url']).read()
|
||||
dest = os.path.join(env.lib_dir, 'cacert.pem')
|
||||
with open(dest, 'wb') as f:
|
||||
f.write(cdata)
|
||||
|
||||
|
||||
def copy_python(env):
|
||||
print('Copying python...')
|
||||
srcdir = j(PREFIX, 'lib/python' + py_ver)
|
||||
@@ -220,6 +229,7 @@ def main():
|
||||
build_launcher(env)
|
||||
files = find_binaries(env)
|
||||
fix_permissions(files)
|
||||
add_ca_certs(env)
|
||||
if not args.dont_strip:
|
||||
strip_binaries(files)
|
||||
if not args.skip_tests:
|
||||
|
||||
@@ -170,6 +170,7 @@ class Freeze(object):
|
||||
self.add_stdlib()
|
||||
self.add_misc_libraries()
|
||||
self.freeze_python()
|
||||
self.add_ca_certs()
|
||||
if not self.dont_strip:
|
||||
self.strip_files()
|
||||
if not self.skip_tests:
|
||||
@@ -180,6 +181,15 @@ class Freeze(object):
|
||||
|
||||
return ret
|
||||
|
||||
@flush
|
||||
def add_ca_certs(self):
|
||||
print('\nDownloading CA certs...')
|
||||
from urllib.request import urlopen
|
||||
cdata = urlopen(kitty_constants['cacerts_url']).read()
|
||||
dest = os.path.join(self.contents_dir, 'Resources', 'cacert.pem')
|
||||
with open(dest, 'wb') as f:
|
||||
f.write(cdata)
|
||||
|
||||
@flush
|
||||
def strip_files(self):
|
||||
print('\nStripping files...')
|
||||
|
||||
@@ -4,6 +4,24 @@ Changelog
|
||||
|kitty| is a feature-rich, cross-platform, *fast*, GPU based terminal.
|
||||
To update |kitty|, :doc:`follow the instructions <binary>`.
|
||||
|
||||
0.23.1 [2021-08-17]
|
||||
----------------------
|
||||
|
||||
- macOS: Fix themes kitten failing to download themes because of missing SSL
|
||||
root certificates (:iss:`3936`)
|
||||
|
||||
- A new option :opt:`clipboard_max_size` to control the maximum size
|
||||
of data that kitty will transmit to the system clipboard on behalf of
|
||||
programs running inside it (:iss:`3937`)
|
||||
|
||||
- When matching windows/tabs in kittens or using remote control, allow matching
|
||||
by recency. ``recent:0`` matches the active window/tab, ``recent:1`` matches
|
||||
the previous window/tab and so on
|
||||
|
||||
- themes kitten: Fix only the first custom theme file being loaded correctly
|
||||
(:iss:`3938`)
|
||||
|
||||
|
||||
0.23.0 [2021-08-16]
|
||||
----------------------
|
||||
|
||||
|
||||
@@ -335,7 +335,7 @@ class Themes:
|
||||
d = parse_theme(name, raw)
|
||||
except (Exception, SystemExit):
|
||||
continue
|
||||
t = Theme(lambda: raw)
|
||||
t = Theme(raw.__str__)
|
||||
t.apply_dict(d)
|
||||
if t.name:
|
||||
self.themes[t.name] = t
|
||||
|
||||
@@ -267,6 +267,17 @@ class Boss:
|
||||
if w is not None:
|
||||
yield w
|
||||
return
|
||||
if field == 'recent':
|
||||
tab = self.active_tab
|
||||
if tab is not None:
|
||||
try:
|
||||
num = int(exp)
|
||||
except Exception:
|
||||
return
|
||||
w = self.window_id_map.get(tab.nth_active_window_id(num))
|
||||
if w is not None:
|
||||
yield w
|
||||
return
|
||||
if field != 'env':
|
||||
pat: MatchPatternType = re.compile(exp)
|
||||
else:
|
||||
@@ -310,6 +321,17 @@ class Boss:
|
||||
idx = (int(pat.pattern) + len(tm.tabs)) % len(tm.tabs)
|
||||
found = True
|
||||
yield tm.tabs[idx]
|
||||
elif field == 'recent':
|
||||
tm = self.active_tab_manager
|
||||
if tm is not None and len(tm.tabs) > 0:
|
||||
try:
|
||||
num = int(exp)
|
||||
except Exception:
|
||||
return
|
||||
q = tm.nth_active_tab(num)
|
||||
if q is not None:
|
||||
found = True
|
||||
yield q
|
||||
if not found:
|
||||
tabs = {self.tab_for_window(w) for w in self.match_windows(match)}
|
||||
for q in tabs:
|
||||
|
||||
@@ -23,7 +23,7 @@ class Version(NamedTuple):
|
||||
|
||||
appname: str = 'kitty'
|
||||
kitty_face = '🐱'
|
||||
version: Version = Version(0, 23, 0)
|
||||
version: Version = Version(0, 23, 1)
|
||||
str_version: str = '.'.join(map(str, version))
|
||||
_plat = sys.platform.lower()
|
||||
is_macos: bool = 'darwin' in _plat
|
||||
|
||||
@@ -2533,10 +2533,17 @@ control exactly which actions are allowed. The set of possible actions is:
|
||||
write-clipboard read-clipboard write-primary read-primary. The default is to
|
||||
allow writing to the clipboard and primary selection. Note 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.
|
||||
running on a remote server via SSH can read your clipboard. See also :opt:`
|
||||
clipboard_max_size`.
|
||||
'''
|
||||
)
|
||||
|
||||
opt('clipboard_max_size', 64, option_type='positive_float', long_text='''
|
||||
The maximum size (in MB) of data from programs running in kitty that will be
|
||||
stored for writing to the system clipboard. See also :opt:`clipboard_control`.
|
||||
A value of zero means no size limit is applied.
|
||||
''')
|
||||
|
||||
opt('allow_hyperlinks', 'yes',
|
||||
option_type='allow_hyperlinks', ctype='bool',
|
||||
long_text='''
|
||||
|
||||
3
kitty/options/parse.py
generated
3
kitty/options/parse.py
generated
@@ -102,6 +102,9 @@ class Parser:
|
||||
def clipboard_control(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['clipboard_control'] = clipboard_control(val)
|
||||
|
||||
def clipboard_max_size(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['clipboard_max_size'] = positive_float(val)
|
||||
|
||||
def close_on_child_death(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['close_on_child_death'] = to_bool(val)
|
||||
|
||||
|
||||
2
kitty/options/types.py
generated
2
kitty/options/types.py
generated
@@ -65,6 +65,7 @@ option_names = ( # {{{
|
||||
'clear_all_shortcuts',
|
||||
'click_interval',
|
||||
'clipboard_control',
|
||||
'clipboard_max_size',
|
||||
'close_on_child_death',
|
||||
'color0',
|
||||
'color1',
|
||||
@@ -458,6 +459,7 @@ class Options:
|
||||
clear_all_shortcuts: bool = False
|
||||
click_interval: float = -1.0
|
||||
clipboard_control: typing.Tuple[str, ...] = ('write-clipboard', 'write-primary')
|
||||
clipboard_max_size: float = 64.0
|
||||
close_on_child_death: bool = False
|
||||
command_on_bell: typing.List[str] = ['none']
|
||||
confirm_os_window_close: int = 0
|
||||
|
||||
@@ -76,13 +76,15 @@ ArgsType = List[str]
|
||||
MATCH_WINDOW_OPTION = '''\
|
||||
--match -m
|
||||
The window to match. Match specifications are of the form:
|
||||
:italic:`field:regexp`. Where field can be one of: id, title, pid, cwd, cmdline, num, env.
|
||||
:italic:`field:regexp`. Where field can be one of: id, title, pid, cwd, cmdline, num, env and recent.
|
||||
You can use the :italic:`ls` command to get a list of windows. Note that for
|
||||
numeric fields such as id, pid and num the expression is interpreted as a number,
|
||||
numeric fields such as id, pid, recent and num the expression is interpreted as a number,
|
||||
not a regular expression. The field num refers to the window position in the current tab,
|
||||
starting from zero and counting clockwise (this is the same as the order in which the
|
||||
windows are reported by the :italic:`ls` command). The window id of the current window
|
||||
is available as the KITTY_WINDOW_ID environment variable. When using the :italic:`env` field
|
||||
is available as the KITTY_WINDOW_ID environment variable. The field recent refers to recently
|
||||
active windows in the currently active tab, with zero being the currently active window, one being the previously active
|
||||
window and so on. When using the :italic:`env` field
|
||||
to match on environment variables you can specify only the environment variable name or a name
|
||||
and value, for example, :italic:`env:MY_ENV_VAR=2`
|
||||
'''
|
||||
@@ -90,14 +92,16 @@ MATCH_TAB_OPTION = '''\
|
||||
--match -m
|
||||
The tab to match. Match specifications are of the form:
|
||||
:italic:`field:regexp`. Where field can be one of:
|
||||
id, index, title, window_id, window_title, pid, cwd, env, cmdline.
|
||||
id, index, title, window_id, window_title, pid, cwd, env, cmdline and recent.
|
||||
You can use the :italic:`ls` command to get a list of tabs. Note that for
|
||||
numeric fields such as id, index and pid the expression is interpreted as a number,
|
||||
numeric fields such as id, recent, index and pid the expression is interpreted as a number,
|
||||
not a regular expression. When using title or id, first a matching tab is
|
||||
looked for and if not found a matching window is looked for, and the tab
|
||||
for that window is used. You can also use window_id and window_title to match
|
||||
the tab that contains the window with the specified id or title. The index number
|
||||
is used to match the nth tab in the currently active OS window.
|
||||
is used to match the nth tab in the currently active OS window. The recent number
|
||||
matches recently active tabs in the currently active OS window, with zero being the currently
|
||||
active tab, one the previously active tab and so on.
|
||||
'''
|
||||
|
||||
|
||||
|
||||
@@ -511,6 +511,12 @@ class Tab: # {{{
|
||||
if groups:
|
||||
return groups[0]
|
||||
|
||||
def nth_active_window_id(self, n: int = 0) -> int:
|
||||
if n <= 0:
|
||||
return self.active_window.id if self.active_window else 0
|
||||
ids = tuple(reversed(self.windows.active_window_history))
|
||||
return ids[min(n - 1, len(ids) - 1)] if ids else 0
|
||||
|
||||
def neighboring_group_id(self, which: EdgeLiteral) -> Optional[int]:
|
||||
neighbors = self.current_layout.neighbors(self.windows)
|
||||
candidates = neighbors.get(which)
|
||||
@@ -748,6 +754,12 @@ class TabManager: # {{{
|
||||
self.set_active_tab_idx(idx)
|
||||
break
|
||||
|
||||
def nth_active_tab(self, n: int = 0) -> Optional[Tab]:
|
||||
if n <= 0:
|
||||
return self.active_tab
|
||||
tab_ids = tuple(reversed(self.active_tab_history))
|
||||
return self.tab_for_id(tab_ids[min(n - 1, len(tab_ids) - 1)]) if tab_ids else None
|
||||
|
||||
def __iter__(self) -> Iterator[Tab]:
|
||||
return iter(self.tabs)
|
||||
|
||||
|
||||
@@ -791,7 +791,8 @@ class Window:
|
||||
self.clipboard_pending = ClipboardPending(where, text)
|
||||
else:
|
||||
self.clipboard_pending = self.clipboard_pending._replace(data=self.clipboard_pending[1] + text)
|
||||
if len(self.clipboard_pending.data) > 8 * 1024 * 1024:
|
||||
limit = get_options().clipboard_max_size
|
||||
if limit and len(self.clipboard_pending.data) > limit * 1024 * 1024:
|
||||
log_error('Discarding part of too large OSC 52 paste request')
|
||||
self.clipboard_pending = self.clipboard_pending._replace(data='', truncated=True)
|
||||
return
|
||||
|
||||
@@ -20,9 +20,9 @@ class TestBuild(BaseTest):
|
||||
|
||||
def test_loading_extensions(self) -> None:
|
||||
import kitty.fast_data_types as fdt
|
||||
from kittens.unicode_input import unicode_names
|
||||
from kittens.choose import subseq_matcher
|
||||
from kittens.diff import diff_speedup
|
||||
from kittens.unicode_input import unicode_names
|
||||
del fdt, unicode_names, subseq_matcher, diff_speedup
|
||||
|
||||
def test_loading_shaders(self) -> None:
|
||||
@@ -31,7 +31,7 @@ class TestBuild(BaseTest):
|
||||
load_shaders(name)
|
||||
|
||||
def test_glfw_modules(self) -> None:
|
||||
from kitty.constants import is_macos, glfw_path
|
||||
from kitty.constants import glfw_path, is_macos
|
||||
linux_backends = ['x11']
|
||||
if not self.is_ci:
|
||||
linux_backends.append('wayland')
|
||||
@@ -49,10 +49,18 @@ class TestBuild(BaseTest):
|
||||
self.assertGreater(len(names), 8)
|
||||
|
||||
def test_filesystem_locations(self) -> None:
|
||||
from kitty.constants import terminfo_dir, logo_png_file
|
||||
from kitty.constants import logo_png_file, terminfo_dir
|
||||
self.assertTrue(os.path.isdir(terminfo_dir), f'Terminfo dir: {terminfo_dir}')
|
||||
self.assertTrue(os.path.exists(logo_png_file), f'Logo file: {logo_png_file}')
|
||||
|
||||
def test_ca_certificates(self):
|
||||
import ssl
|
||||
import sys
|
||||
if not getattr(sys, 'frozen', False):
|
||||
self.skipTest('CA certificates are only tested on frozen builds')
|
||||
c = ssl.create_default_context()
|
||||
self.assertGreater(c.cert_store_stats()['x509_ca'], 2)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
tests = unittest.defaultTestLoader.loadTestsFromTestCase(TestBuild)
|
||||
|
||||
Reference in New Issue
Block a user