1#!/usr/local/bin/python3.8 2# vim:fileencoding=utf-8 3# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> 4 5import errno 6import os 7import pwd 8import sys 9from contextlib import suppress 10from typing import NamedTuple, Optional, Set, TYPE_CHECKING 11 12from .types import run_once 13 14if TYPE_CHECKING: 15 from .options.types import Options 16 17 18class Version(NamedTuple): 19 major: int 20 minor: int 21 patch: int 22 23 24appname: str = 'kitty' 25kitty_face = '' 26version: Version = Version(0, 23, 1) 27str_version: str = '.'.join(map(str, version)) 28_plat = sys.platform.lower() 29is_macos: bool = 'darwin' in _plat 30if getattr(sys, 'frozen', False): 31 extensions_dir: str = getattr(sys, 'kitty_extensions_dir') 32 kitty_base_dir = os.path.dirname(extensions_dir) 33 if is_macos: 34 kitty_base_dir = os.path.dirname(os.path.dirname(kitty_base_dir)) 35 kitty_base_dir = os.path.join(kitty_base_dir, 'kitty') 36else: 37 kitty_base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 38 extensions_dir = os.path.join(kitty_base_dir, 'kitty') 39 40 41@run_once 42def kitty_exe() -> str: 43 rpath = sys._xoptions.get('bundle_exe_dir') 44 if not rpath: 45 items = os.environ.get('PATH', '').split(os.pathsep) + [os.path.join(kitty_base_dir, 'kitty', 'launcher')] 46 seen: Set[str] = set() 47 for candidate in filter(None, items): 48 if candidate not in seen: 49 seen.add(candidate) 50 if os.access(os.path.join(candidate, 'kitty'), os.X_OK): 51 rpath = candidate 52 break 53 else: 54 raise RuntimeError('kitty binary not found') 55 return os.path.join(rpath, 'kitty') 56 57 58def _get_config_dir() -> str: 59 if 'KITTY_CONFIG_DIRECTORY' in os.environ: 60 return os.path.abspath(os.path.expanduser(os.environ['KITTY_CONFIG_DIRECTORY'])) 61 62 locations = [] 63 if 'XDG_CONFIG_HOME' in os.environ: 64 locations.append(os.path.abspath(os.path.expanduser(os.environ['XDG_CONFIG_HOME']))) 65 locations.append(os.path.expanduser('~/.config')) 66 if is_macos: 67 locations.append(os.path.expanduser('~/Library/Preferences')) 68 for loc in filter(None, os.environ.get('XDG_CONFIG_DIRS', '').split(os.pathsep)): 69 locations.append(os.path.abspath(os.path.expanduser(loc))) 70 for loc in locations: 71 if loc: 72 q = os.path.join(loc, appname) 73 if os.access(q, os.W_OK) and os.path.exists(os.path.join(q, 'kitty.conf')): 74 return q 75 76 def make_tmp_conf() -> None: 77 import tempfile 78 import atexit 79 ans = tempfile.mkdtemp(prefix='kitty-conf-') 80 81 def cleanup() -> None: 82 import shutil 83 with suppress(Exception): 84 shutil.rmtree(ans) 85 atexit.register(cleanup) 86 87 candidate = os.path.abspath(os.path.expanduser(os.environ.get('XDG_CONFIG_HOME') or '~/.config')) 88 ans = os.path.join(candidate, appname) 89 try: 90 os.makedirs(ans, exist_ok=True) 91 except FileExistsError: 92 raise SystemExit('A file {} already exists. It must be a directory, not a file.'.format(ans)) 93 except PermissionError: 94 make_tmp_conf() 95 except OSError as err: 96 if err.errno != errno.EROFS: # Error other than read-only file system 97 raise 98 make_tmp_conf() 99 return ans 100 101 102config_dir = _get_config_dir() 103del _get_config_dir 104defconf = os.path.join(config_dir, 'kitty.conf') 105 106 107@run_once 108def cache_dir() -> str: 109 if 'KITTY_CACHE_DIRECTORY' in os.environ: 110 candidate = os.path.abspath(os.environ['KITTY_CACHE_DIRECTORY']) 111 elif is_macos: 112 candidate = os.path.join(os.path.expanduser('~/Library/Caches'), appname) 113 else: 114 candidate = os.environ.get('XDG_CACHE_HOME', '~/.cache') 115 candidate = os.path.join(os.path.expanduser(candidate), appname) 116 os.makedirs(candidate, exist_ok=True) 117 return candidate 118 119 120def wakeup() -> None: 121 from .fast_data_types import get_boss 122 b = get_boss() 123 if b is not None: 124 b.child_monitor.wakeup() 125 126 127terminfo_dir = os.path.join(kitty_base_dir, 'terminfo') 128logo_png_file = os.path.join(kitty_base_dir, 'logo', 'kitty.png') 129beam_cursor_data_file = os.path.join(kitty_base_dir, 'logo', 'beam-cursor.png') 130try: 131 shell_path = pwd.getpwuid(os.geteuid()).pw_shell or '/bin/sh' 132except KeyError: 133 with suppress(Exception): 134 print('Failed to read login shell via getpwuid() for current user, falling back to /bin/sh', file=sys.stderr) 135 shell_path = '/bin/sh' 136 137 138def glfw_path(module: str) -> str: 139 prefix = 'kitty.' if getattr(sys, 'frozen', False) else '' 140 return os.path.join(extensions_dir, f'{prefix}glfw-{module}.so') 141 142 143def detect_if_wayland_ok() -> bool: 144 if 'WAYLAND_DISPLAY' not in os.environ: 145 return False 146 if 'KITTY_DISABLE_WAYLAND' in os.environ: 147 return False 148 wayland = glfw_path('wayland') 149 if not os.path.exists(wayland): 150 return False 151 return True 152 153 154def is_wayland(opts: Optional['Options'] = None) -> bool: 155 if is_macos: 156 return False 157 if opts is None: 158 return bool(getattr(is_wayland, 'ans')) 159 if opts.linux_display_server == 'auto': 160 ans = detect_if_wayland_ok() 161 else: 162 ans = opts.linux_display_server == 'wayland' 163 setattr(is_wayland, 'ans', ans) 164 return ans 165 166 167supports_primary_selection = not is_macos 168 169 170def running_in_kitty(set_val: Optional[bool] = None) -> bool: 171 if set_val is not None: 172 setattr(running_in_kitty, 'ans', set_val) 173 return bool(getattr(running_in_kitty, 'ans', False)) 174 175 176def resolve_custom_file(path: str) -> str: 177 path = os.path.expandvars(os.path.expanduser(path)) 178 if not os.path.isabs(path): 179 path = os.path.join(config_dir, path) 180 return path 181 182 183def read_kitty_resource(name: str) -> bytes: 184 try: 185 from importlib.resources import read_binary 186 except ImportError: 187 from importlib_resources import read_binary # type: ignore 188 return read_binary('kitty', name) 189 190 191def website_url(doc_name: str = '') -> str: 192 if doc_name: 193 doc_name = doc_name.rstrip('/') 194 if doc_name: 195 doc_name += '/' 196 return f'https://sw.kovidgoyal.net/kitty/{doc_name}' 197