123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272 |
- #!/usr/bin/env python
- # License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
- import re
- import sys
- from binascii import hexlify, unhexlify
- from contextlib import suppress
- from typing import Dict, Optional, Type
- from kitty.constants import appname, str_version
- from kitty.options.types import Options
- from kitty.terminfo import names
- class Query:
- name: str = ''
- ans: str = ''
- help_text: str = ''
- override_query_name: str = ''
- @property
- def query_name(self) -> str:
- return self.override_query_name or f'kitty-query-{self.name}'
- def __init__(self) -> None:
- self.encoded_query_name = hexlify(self.query_name.encode('utf-8')).decode('ascii')
- self.pat = re.compile(f'\x1bP([01])\\+r{self.encoded_query_name}(.*?)\x1b\\\\'.encode('ascii'))
- def query_code(self) -> str:
- return f"\x1bP+q{self.encoded_query_name}\x1b\\"
- def decode_response(self, res: bytes) -> str:
- return unhexlify(res).decode('utf-8')
- def more_needed(self, buffer: bytes) -> bool:
- m = self.pat.search(buffer)
- if m is None:
- return True
- if m.group(1) == b'1':
- q = m.group(2)
- if q.startswith(b'='):
- with suppress(Exception):
- self.ans = self.decode_response(memoryview(q)[1:])
- return False
- def output_line(self) -> str:
- return self.ans
- @staticmethod
- def get_result(opts: Options, window_id: int, os_window_id: int) -> str:
- raise NotImplementedError()
- all_queries: Dict[str, Type[Query]] = {}
- def query(cls: Type[Query]) -> Type[Query]:
- all_queries[cls.name] = cls
- return cls
- @query
- class TerminalName(Query):
- name: str = 'name'
- override_query_name: str = 'name'
- help_text: str = f'Terminal name (e.g. :code:`{names[0]}`)'
- @staticmethod
- def get_result(opts: Options, window_id: int, os_window_id: int) -> str:
- return appname
- @query
- class TerminalVersion(Query):
- name: str = 'version'
- help_text: str = f'Terminal version (e.g. :code:`{str_version}`)'
- @staticmethod
- def get_result(opts: Options, window_id: int, os_window_id: int) -> str:
- return str_version
- @query
- class AllowHyperlinks(Query):
- name: str = 'allow_hyperlinks'
- help_text: str = 'The config option :opt:`allow_hyperlinks` in :file:`kitty.conf` for allowing hyperlinks can be :code:`yes`, :code:`no` or :code:`ask`'
- @staticmethod
- def get_result(opts: Options, window_id: int, os_window_id: int) -> str:
- return 'ask' if opts.allow_hyperlinks == 0b11 else ('yes' if opts.allow_hyperlinks else 'no')
- @query
- class FontFamily(Query):
- name: str = 'font_family'
- help_text: str = 'The current font\'s PostScript name'
- @staticmethod
- def get_result(opts: Options, window_id: int, os_window_id: int) -> str:
- from kitty.fast_data_types import current_fonts
- cf = current_fonts(os_window_id)
- return cf['medium'].postscript_name()
- @query
- class BoldFont(Query):
- name: str = 'bold_font'
- help_text: str = 'The current bold font\'s PostScript name'
- @staticmethod
- def get_result(opts: Options, window_id: int, os_window_id: int) -> str:
- from kitty.fast_data_types import current_fonts
- cf = current_fonts(os_window_id)
- return cf['bold'].postscript_name()
- @query
- class ItalicFont(Query):
- name: str = 'italic_font'
- help_text: str = 'The current italic font\'s PostScript name'
- @staticmethod
- def get_result(opts: Options, window_id: int, os_window_id: int) -> str:
- from kitty.fast_data_types import current_fonts
- cf = current_fonts(os_window_id)
- return cf['italic'].postscript_name()
- @query
- class BiFont(Query):
- name: str = 'bold_italic_font'
- help_text: str = 'The current bold-italic font\'s PostScript name'
- @staticmethod
- def get_result(opts: Options, window_id: int, os_window_id: int) -> str:
- from kitty.fast_data_types import current_fonts
- cf = current_fonts(os_window_id)
- return cf['bi'].postscript_name()
- @query
- class FontSize(Query):
- name: str = 'font_size'
- help_text: str = 'The current font size in pts'
- @staticmethod
- def get_result(opts: Options, window_id: int, os_window_id: int) -> str:
- from kitty.fast_data_types import current_fonts
- cf = current_fonts(os_window_id)
- return f'{cf["font_sz_in_pts"]:g}'
- @query
- class DpiX(Query):
- name: str = 'dpi_x'
- help_text: str = 'The current DPI on the x-axis'
- @staticmethod
- def get_result(opts: Options, window_id: int, os_window_id: int) -> str:
- from kitty.fast_data_types import current_fonts
- cf = current_fonts(os_window_id)
- return f'{cf["logical_dpi_x"]:g}'
- @query
- class DpiY(Query):
- name: str = 'dpi_y'
- help_text: str = 'The current DPI on the y-axis'
- @staticmethod
- def get_result(opts: Options, window_id: int, os_window_id: int) -> str:
- from kitty.fast_data_types import current_fonts
- cf = current_fonts(os_window_id)
- return f'{cf["logical_dpi_y"]:g}'
- @query
- class Foreground(Query):
- name: str = 'foreground'
- help_text: str = 'The current foreground color as a 24-bit # color code'
- @staticmethod
- def get_result(opts: Options, window_id: int, os_window_id: int) -> str:
- from kitty.fast_data_types import Color, get_boss
- boss = get_boss()
- w = boss.window_id_map.get(window_id)
- if w is None:
- return opts.foreground.as_sharp
- col = w.screen.color_profile.default_fg
- r, g, b = col >> 16, (col >> 8) & 0xff, col & 0xff
- return Color(r, g, b).as_sharp
- @query
- class Background(Query):
- name: str = 'background'
- help_text: str = 'The current background color as a 24-bit # color code'
- @staticmethod
- def get_result(opts: Options, window_id: int, os_window_id: int) -> str:
- from kitty.fast_data_types import Color, get_boss
- boss = get_boss()
- w = boss.window_id_map.get(window_id)
- if w is None:
- return opts.background.as_sharp
- col = w.screen.color_profile.default_bg
- r, g, b = col >> 16, (col >> 8) & 0xff, col & 0xff
- return Color(r, g, b).as_sharp
- @query
- class ClipboardControl(Query):
- name: str = 'clipboard_control'
- help_text: str = 'The config option :opt:`clipboard_control` in :file:`kitty.conf` for allowing reads/writes to/from the clipboard'
- @staticmethod
- def get_result(opts: Options, window_id: int, os_window_id: int) -> str:
- return ' '.join(opts.clipboard_control)
- def get_result(name: str, window_id: int, os_window_id: int) -> Optional[str]:
- from kitty.fast_data_types import get_options
- q = all_queries.get(name)
- if q is None:
- return None
- return q.get_result(get_options(), window_id, os_window_id)
- def options_spec() -> str:
- return '''\
- --wait-for
- type=float
- default=10
- The amount of time (in seconds) to wait for a response from the terminal, after
- querying it.
- '''
- help_text = '''\
- Query the terminal this kitten is run in for various capabilities. This sends
- escape codes to the terminal and based on its response prints out data about
- supported capabilities. Note that this is a blocking operation, since it has to
- wait for a response from the terminal. You can control the maximum wait time via
- the :code:`--wait-for` option.
- The output is lines of the form::
- query: data
- If a particular :italic:`query` is unsupported by the running kitty version, the
- :italic:`data` will be blank.
- Note that when calling this from another program, be very careful not to perform
- any I/O on the terminal device until this kitten exits.
- Available queries are:
- {}
- '''.format('\n'.join(
- f':code:`{name}`:\n {c.help_text}\n' for name, c in all_queries.items()))
- usage = '[query1 query2 ...]'
- if __name__ == '__main__':
- raise SystemExit('Should be run as kitten hints')
- elif __name__ == '__doc__':
- cd = sys.cli_docs # type: ignore
- cd['usage'] = usage
- cd['options'] = options_spec
- cd['help_text'] = help_text
- cd['short_desc'] = 'Query the terminal for various capabilities'
|