kitty/kitty/main.py
2017-09-25 17:47:30 +05:30

290 lines
9.5 KiB
Python

#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import argparse
import locale
import os
import sys
from contextlib import contextmanager
from gettext import gettext as _
from .boss import Boss
from .config import (
cached_values, load_cached_values, load_config, save_cached_values
)
from .constants import (
appname, config_dir, isosx, logo_data_file, str_version, viewport_size
)
from .fast_data_types import (
GL_VERSION_REQUIRED, GLFW_CONTEXT_VERSION_MAJOR,
GLFW_CONTEXT_VERSION_MINOR, GLFW_DECORATED, GLFW_OPENGL_CORE_PROFILE,
GLFW_OPENGL_FORWARD_COMPAT, GLFW_OPENGL_PROFILE, GLFW_SAMPLES,
GLFW_STENCIL_BITS, GLFWWindow, change_wcwidth, check_for_extensions,
clear_buffers, glewInit, glfw_init, glfw_init_hint_string,
glfw_swap_interval, glfw_terminate, glfw_window_hint, set_logical_dpi,
set_options
)
from .layout import all_layouts
from .utils import color_as_int, detach, get_logical_dpi, safe_print
try:
from .fast_data_types import GLFW_X11_WM_CLASS_NAME, GLFW_X11_WM_CLASS_CLASS
except ImportError:
GLFW_X11_WM_CLASS_NAME = GLFW_X11_WM_CLASS_CLASS = None
defconf = os.path.join(config_dir, 'kitty.conf')
def option_parser():
parser = argparse.ArgumentParser(
prog=appname,
description=_('The {} terminal emulator').format(appname)
)
a = parser.add_argument
a(
'--class',
default=appname,
dest='cls',
help=_('Set the WM_CLASS property')
)
a(
'--config',
action='append',
help=_(
'Specify a path to the config file(s) to use.'
' Can be specified multiple times to read multiple'
' config files in sequence, which are merged. Default: {}'
).format(defconf)
)
a(
'--override',
'-o',
action='append',
help=_(
'Override individual configuration options, can be specified'
' multiple times. Syntax: name=value. For example: {}'
).format('-o font_size=20')
)
a(
'--cmd',
'-c',
default=None,
help=_('Run python code in the kitty context')
)
a(
'-d',
'--directory',
default='.',
help=_('Change to the specified directory when launching')
)
a(
'--version',
'-v',
action='version',
version='{} {} by Kovid Goyal'.format(appname, str_version)
)
a(
'--dump-commands',
action='store_true',
default=False,
help=_('Output commands received from child process to stdout')
)
if not isosx:
a(
'--detach',
action='store_true',
default=False,
help=_('Detach from the controlling terminal, if any')
)
a(
'--replay-commands',
default=None,
help=_('Replay previously dumped commands')
)
a(
'--dump-bytes',
help=_('Path to file in which to store the raw bytes received from the'
' child process. Useful for debugging.')
)
a(
'--debug-kitty-gl',
action='store_true',
default=False,
help=_('Debug OpenGL commands. This will cause all OpenGL calls'
' to check for errors instead of ignoring them. Useful'
' when debugging rendering problems.')
)
a(
'--window-layout',
default=None,
choices=frozenset(all_layouts.keys()),
help=_('The window layout to use on startup')
)
a(
'--session',
default=None,
help=_(
'Path to a file containing the startup session (tabs, windows, layout, programs)'
)
)
a(
'args',
nargs=argparse.REMAINDER,
help=_(
'The remaining arguments are used to launch a program other than the default shell. Any further options are passed'
' directly to the program being invoked.'
)
)
return parser
def setup_opengl(opts):
if opts.macos_hide_titlebar:
glfw_window_hint(GLFW_DECORATED, False)
glfw_window_hint(GLFW_CONTEXT_VERSION_MAJOR, GL_VERSION_REQUIRED[0])
glfw_window_hint(GLFW_CONTEXT_VERSION_MINOR, GL_VERSION_REQUIRED[1])
glfw_window_hint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE)
glfw_window_hint(GLFW_OPENGL_FORWARD_COMPAT, True)
glfw_window_hint(GLFW_SAMPLES, 0)
if isosx:
# OS X cannot handle 16bit stencil buffers
glfw_window_hint(GLFW_STENCIL_BITS, 8)
def run_app(opts, args):
set_options(opts)
setup_opengl(opts)
load_cached_values()
if 'window-size' in cached_values and opts.remember_window_size:
ws = cached_values['window-size']
try:
viewport_size.width, viewport_size.height = map(int, ws)
except Exception:
safe_print('Invalid cached window size, ignoring', file=sys.stderr)
viewport_size.width = max(100, viewport_size.width)
viewport_size.height = max(80, viewport_size.height)
else:
viewport_size.width = opts.initial_window_width
viewport_size.height = opts.initial_window_height
try:
window = GLFWWindow(viewport_size.width, viewport_size.height, args.cls)
except ValueError:
safe_print('Failed to create GLFW window with initial size:', viewport_size)
viewport_size.width = 640
viewport_size.height = 400
window = GLFWWindow(viewport_size.width, viewport_size.height, args.cls)
window.make_context_current()
if isosx:
from .fast_data_types import cocoa_make_window_resizable, cocoa_create_global_menu, cocoa_init
check_for_extensions()
cocoa_init()
cocoa_create_global_menu()
if opts.macos_hide_titlebar:
cocoa_make_window_resizable(window.cocoa_window_id())
else:
with open(logo_data_file, 'rb') as f:
window.set_icon(f.read(), 256, 256)
set_logical_dpi(*get_logical_dpi())
viewport_size.width, viewport_size.height = window.get_framebuffer_size()
w, h = window.get_window_size()
viewport_size.x_ratio = viewport_size.width / float(w)
viewport_size.y_ratio = viewport_size.height / float(h)
glewInit()
boss = Boss(window, opts, args)
boss.start()
glfw_swap_interval(0)
clear_buffers(window.swap_buffers, color_as_int(opts.background))
# We dont turn this on as it causes rendering performance to be much worse,
# for example, dragging the mouse to select is laggy
# glfw_swap_interval(1)
try:
boss.child_monitor.main_loop()
finally:
boss.destroy()
del window
cached_values['window-size'] = viewport_size.width, viewport_size.height
save_cached_values()
def ensure_osx_locale():
# Ensure the LANG env var is set. See
# https://github.com/kovidgoyal/kitty/issues/90
from .fast_data_types import cocoa_get_lang
if 'LANG' not in os.environ:
lang = cocoa_get_lang()
if lang is not None:
os.environ['LANG'] = lang + '.UTF-8'
@contextmanager
def setup_profiling(args):
try:
from .fast_data_types import start_profiler, stop_profiler
except ImportError:
start_profiler = stop_profiler = None
if start_profiler is not None:
start_profiler('/tmp/kitty-profile.log')
yield
if stop_profiler is not None:
import subprocess
stop_profiler()
exe = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'kitty-profile')
cg = '/tmp/kitty-profile.callgrind'
print('Post processing profile data for', exe, '...')
subprocess.check_call(['pprof', '--callgrind', exe, '/tmp/kitty-profile.log'], stdout=open(cg, 'wb'))
try:
subprocess.Popen(['kcachegrind', cg])
except FileNotFoundError:
subprocess.check_call(['pprof', '--text', exe, '/tmp/kitty-profile.log'])
print('To view the graphical call data, use: kcachegrind', cg)
def main():
try:
sys.setswitchinterval(1000.0) # we have only a single python thread
except AttributeError:
pass # python compiled without threading
if isosx:
ensure_osx_locale()
try:
locale.setlocale(locale.LC_ALL, '')
except Exception:
if not isosx:
raise
print('Failed to set locale with LANG:', os.environ.get('LANG'), file=sys.stderr)
os.environ.pop('LANG')
try:
locale.setlocale(locale.LC_ALL, '')
except Exception:
print('Failed to set locale with no LANG, ignoring', file=sys.stderr)
if os.environ.pop('KITTY_LAUNCHED_BY_LAUNCH_SERVICES',
None) == '1' and getattr(sys, 'frozen', True):
os.chdir(os.path.expanduser('~'))
if not os.path.isdir(os.getcwd()):
os.chdir(os.path.expanduser('~'))
args = option_parser().parse_args()
if getattr(args, 'detach', False):
detach()
if args.cmd:
exec(args.cmd)
return
if args.replay_commands:
from kitty.client import main
main(args.replay_commands)
return
config = args.config or (defconf, )
overrides = (a.replace('=', ' ', 1) for a in args.override or ())
opts = load_config(*config, overrides=overrides)
change_wcwidth(not opts.use_system_wcwidth)
if GLFW_X11_WM_CLASS_CLASS is not None:
glfw_init_hint_string(GLFW_X11_WM_CLASS_CLASS, opts.cls)
if not glfw_init():
raise SystemExit('GLFW initialization failed')
try:
with setup_profiling(args):
run_app(opts, args)
finally:
glfw_terminate()