Compare commits

...

12 Commits

Author SHA1 Message Date
Kovid Goyal
cd7b4fcd8e version 0.23.1 2021-08-17 11:37:24 +05:30
Kovid Goyal
f67995d5d3 See if we can mollify CodeQL 2021-08-16 22:53:46 +05:30
Kovid Goyal
f0e7344bc8 Use bundled CA certs on Linux binary builds as well
Makes it consistent with macOS
2021-08-16 22:47:53 +05:30
Kovid Goyal
aa525c68c7 Only test bundled CA certs on macOS 2021-08-16 22:26:10 +05:30
Kovid Goyal
a2e25331a5 Dont check Linux CA locations on macOS 2021-08-16 22:23:24 +05:30
Kovid Goyal
d8f09d377f Install ca-certificates on Linux CI machines 2021-08-16 22:15:27 +05:30
Kovid Goyal
cc96cb1c75 Check a few more locations for CA certificates 2021-08-16 22:09:39 +05:30
Kovid Goyal
5966f04656 Add a build test for CA certificate loading 2021-08-16 20:44:47 +05:30
Kovid Goyal
dbce9a8f29 themes kitten: Fix only the first custom theme file being loaded
Fixes #3938
2021-08-16 20:27:21 +05:30
Kovid Goyal
4333552523 Matching windows/tabs: allow matching by recency
``recent:0`` matches the active window/tab, ``recent:1`` matches the
previous window/tab and so on
2021-08-16 18:29:06 +05:30
Kovid Goyal
c00e945f6e Make the clipboard size limit configurable
Fixes #3937
2021-08-16 17:29:36 +05:30
Kovid Goyal
56cb628ee8 macOS: Bundle mozilla's root certificates with kitty
Apple doesnt provide root certificates in a form useable by openssl
which means all ssl based network requests fail, so bundle our own

Fixes #3936
2021-08-16 17:16:03 +05:30
16 changed files with 124 additions and 26 deletions

View File

@@ -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:

View File

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

View File

@@ -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

View File

@@ -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:

View File

@@ -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...')

View File

@@ -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]
----------------------

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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='''

View File

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

View File

@@ -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

View File

@@ -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.
'''

View File

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

View File

@@ -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

View File

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