From a14cf81422d100b697b3ab806faadc1703d55f52 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 10 Feb 2018 09:58:15 +0530 Subject: [PATCH] Store cached window sizes in the cache dir rather than the config dir A side-effect is that on the very next start kitty wont remember window sizes, apologies for the on convenience. --- kitty/boss.py | 9 ++-- kitty/config.py | 106 +++++++++++++++++++++++++++------------------ kitty/constants.py | 22 ++++++++++ kitty/main.py | 37 ++++++++-------- 4 files changed, 109 insertions(+), 65 deletions(-) diff --git a/kitty/boss.py b/kitty/boss.py index 5c8c62155..45fdb1499 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -8,7 +8,7 @@ from gettext import gettext as _ from weakref import WeakValueDictionary from .cli import create_opts, parse_args -from .config import MINIMUM_FONT_SIZE, cached_values, initial_window_size +from .config import MINIMUM_FONT_SIZE, initial_window_size from .constants import appname, set_boss, wakeup from .fast_data_types import ( ChildMonitor, create_os_window, current_os_window, destroy_global_data, @@ -62,8 +62,9 @@ class DumpCommands: # {{{ class Boss: - def __init__(self, os_window_id, opts, args): + def __init__(self, os_window_id, opts, args, cached_values): self.window_id_map = WeakValueDictionary() + self.cached_values = cached_values self.os_window_map = {} self.cursor_blinking = True self.shutting_down = False @@ -85,7 +86,7 @@ class Boss: def add_os_window(self, startup_session, os_window_id=None, wclass=None, wname=None, size=None, startup_id=None): dpi_changed = False if os_window_id is None: - w, h = initial_window_size(self.opts) if size is None else size + w, h = initial_window_size(self.opts, self.cached_values) if size is None else size cls = wclass or self.args.cls or appname os_window_id = create_os_window(w, h, appname, wname or self.args.name or cls, cls) if startup_id: @@ -359,7 +360,7 @@ class Boss: w.paste('\n'.join(paths)) def on_os_window_closed(self, os_window_id, viewport_width, viewport_height): - cached_values['window-size'] = viewport_width, viewport_height + self.cached_values['window-size'] = viewport_width, viewport_height tm = self.os_window_map.pop(os_window_id, None) if tm is not None: tm.destroy() diff --git a/kitty/config.py b/kitty/config.py index 4410d4cc8..1d0a683fa 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -10,13 +10,14 @@ import shlex import sys import tempfile from collections import namedtuple +from contextlib import contextmanager from . import fast_data_types as defines from .config_utils import ( init_config, parse_config_base, positive_float, positive_int, to_bool, to_color, unit_float ) -from .constants import config_dir +from .constants import cache_dir from .fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE from .layout import all_layouts from .utils import safe_print @@ -92,7 +93,10 @@ def parse_shortcut(sc): KeyAction = namedtuple('KeyAction', 'func args') -shlex_actions = {'pass_selection_to_program', 'new_window', 'new_tab', 'new_os_window', 'new_window_with_cwd', 'new_tab_with_cwd', 'new_os_window_with_cwd'} +shlex_actions = { + 'pass_selection_to_program', 'new_window', 'new_tab', 'new_os_window', + 'new_window_with_cwd', 'new_tab_with_cwd', 'new_os_window_with_cwd' +} def parse_key_action(action): @@ -108,7 +112,7 @@ def parse_key_action(action): elif func == 'send_text': args = rest.split(' ', 1) elif func == 'goto_tab': - args = (max(0, int(rest)),) + args = (max(0, int(rest)), ) elif func in shlex_actions: args = shlex.split(rest) return KeyAction(func, args) @@ -172,7 +176,8 @@ def parse_symbol_map(val): def parse_send_text_bytes(text): - return ast.literal_eval("'''" + text.replace("'''", "'\\''") + "'''").encode('utf-8') + return ast.literal_eval("'''" + text.replace("'''", "'\\''") + "'''" + ).encode('utf-8') def parse_send_text(val, keymap): @@ -231,7 +236,11 @@ def tab_separator(x): def tab_font_style(x): - return {'bold-italic': (True, True), 'bold': (True, False), 'italic': (False, True)}.get(x.lower().replace('_', '-'), (False, False)) + return { + 'bold-italic': (True, True), + 'bold': (True, False), + 'italic': (False, True) + }.get(x.lower().replace('_', '-'), (False, False)) def tab_bar_edge(x): @@ -242,8 +251,9 @@ def url_style(x): return url_style.map.get(x, url_style.map['curly']) -url_style.map = dict(((v, i) for i, v in enumerate('none single double curly'.split()))) - +url_style.map = dict( + ((v, i) for i, v in enumerate('none single double curly'.split())) +) type_map = { 'allow_remote_control': to_bool, @@ -313,7 +323,9 @@ def special_handling(key, val, ans): defaults = None -default_config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'kitty.conf') +default_config_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), 'kitty.conf' +) def parse_config(lines, check_keys=True): @@ -321,13 +333,21 @@ def parse_config(lines, check_keys=True): 'keymap': {}, 'symbol_map': {}, } - parse_config_base(lines, defaults, type_map, special_handling, ans, check_keys=check_keys) + parse_config_base( + lines, + defaults, + type_map, + special_handling, + ans, + check_keys=check_keys + ) return ans Options, defaults = init_config(default_config_path, parse_config) actions = frozenset(a.func for a in defaults.keymap.values()) | frozenset( - 'combine send_text goto_tab new_tab_with_cwd new_window_with_cwd new_os_window_with_cwd'.split() + 'combine send_text goto_tab new_tab_with_cwd new_window_with_cwd new_os_window_with_cwd'. + split() ) no_op_actions = frozenset({'noop', 'no-op', 'no_op'}) @@ -393,37 +413,12 @@ def build_ansi_color_table(opts: Options = defaults): return list(map(col, range(16))) -cached_values = {} -cached_path = os.path.join(config_dir, 'cached.json') - - -def load_cached_values(): - cached_values.clear() - try: - with open(cached_path, 'rb') as f: - cached_values.update(json.loads(f.read().decode('utf-8'))) - except FileNotFoundError: - pass - except Exception as err: - safe_print( - 'Failed to load cached values with error: {}'.format(err), - file=sys.stderr - ) - - -def save_cached_values(): - fd, p = tempfile.mkstemp( - dir=os.path.dirname(cached_path), suffix='cached.json.tmp' - ) +def atomic_save(data, path): + fd, p = tempfile.mkstemp(dir=os.path.dirname(path), suffix='.tmp') try: with os.fdopen(fd, 'wb') as f: - f.write(json.dumps(cached_values).encode('utf-8')) - os.rename(p, cached_path) - except Exception as err: - safe_print( - 'Failed to save cached values with error: {}'.format(err), - file=sys.stderr - ) + f.write(data) + os.rename(p, path) finally: try: os.remove(p) @@ -431,13 +426,40 @@ def save_cached_values(): pass except Exception as err: safe_print( - 'Failed to delete temp file for saved cached values with error: {}'. - format(err), + 'Failed to delete temp file {} for atomic save with error: {}'. + format(p, err), file=sys.stderr ) -def initial_window_size(opts): +@contextmanager +def cached_values_for(name): + cached_path = os.path.join(cache_dir(), name + '.json') + cached_values = {} + try: + with open(cached_path, 'rb') as f: + cached_values.update(json.loads(f.read().decode('utf-8'))) + except FileNotFoundError: + pass + except Exception as err: + safe_print( + 'Failed to load cached in {} values with error: {}'.format(name, err), + file=sys.stderr + ) + + yield cached_values + + try: + data = json.dumps(cached_values).encode('utf-8') + atomic_save(data, cached_path) + except Exception as err: + safe_print( + 'Failed to save cached values with error: {}'.format(err), + file=sys.stderr + ) + + +def initial_window_size(opts, cached_values): w, h = opts.initial_window_width, opts.initial_window_height if 'window-size' in cached_values and opts.remember_window_size: ws = cached_values['window-size'] diff --git a/kitty/constants.py b/kitty/constants.py index 31ee32b00..5d3553e89 100644 --- a/kitty/constants.py +++ b/kitty/constants.py @@ -36,6 +36,28 @@ del _get_config_dir defconf = os.path.join(config_dir, 'kitty.conf') +def _get_cache_dir(): + if 'KITTY_CACHE_DIRECTORY' in os.environ: + candidate = os.path.abspath(os.environ['KITTY_CACHE_DIRECTORY']) + elif is_macos: + candidate = os.path.join(os.path.expanduser('~/Library/Caches'), appname) + else: + candidate = os.environ.get('XDG_CACHE_HOME', '~/.cache') + candidate = os.path.join(os.path.expanduser(candidate), appname) + try: + os.makedirs(candidate) + except FileExistsError: + pass + return candidate + + +def cache_dir(): + ans = getattr(cache_dir, 'ans', None) + if ans is None: + ans = cache_dir.ans = _get_cache_dir() + return ans + + def get_boss(): return get_boss.boss diff --git a/kitty/main.py b/kitty/main.py index d4e185344..342478ecf 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -11,11 +11,11 @@ from contextlib import contextmanager from .borders import load_borders_program from .boss import Boss from .cli import create_opts, parse_args -from .config import initial_window_size, load_cached_values, save_cached_values +from .config import cached_values_for, initial_window_size from .constants import appname, glfw_path, is_macos, is_wayland, logo_data_file from .fast_data_types import ( - create_os_window, glfw_init, glfw_terminate, - install_sigchld_handler, set_default_window_icon, set_options, show_window + create_os_window, glfw_init, glfw_terminate, install_sigchld_handler, + set_default_window_icon, set_options, show_window ) from .fonts.box_drawing import set_scale from .utils import ( @@ -40,22 +40,21 @@ def init_graphics(): def run_app(opts, args): set_scale(opts.box_drawing_scale) set_options(opts, is_wayland, args.debug_gl) - load_cached_values() - w, h = initial_window_size(opts) - window_id = create_os_window(w, h, appname, args.name or args.cls or appname, args.cls or appname, load_all_shaders) - startup_ctx = init_startup_notification(window_id) - show_window(window_id) - if not is_wayland and not is_macos: # no window icons on wayland - with open(logo_data_file, 'rb') as f: - set_default_window_icon(f.read(), 256, 256) - boss = Boss(window_id, opts, args) - boss.start() - end_startup_notification(startup_ctx) - try: - boss.child_monitor.main_loop() - finally: - boss.destroy() - save_cached_values() + with cached_values_for('main') as cached_values: + w, h = initial_window_size(opts, cached_values) + window_id = create_os_window(w, h, appname, args.name or args.cls or appname, args.cls or appname, load_all_shaders) + startup_ctx = init_startup_notification(window_id) + show_window(window_id) + if not is_wayland and not is_macos: # no window icons on wayland + with open(logo_data_file, 'rb') as f: + set_default_window_icon(f.read(), 256, 256) + boss = Boss(window_id, opts, args, cached_values) + boss.start() + end_startup_notification(startup_ctx) + try: + boss.child_monitor.main_loop() + finally: + boss.destroy() def ensure_osx_locale():