main.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. #!/usr/bin/env python
  2. # License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
  3. import os
  4. import sys
  5. from contextlib import suppress
  6. from functools import partial
  7. from typing import Iterable, Mapping, Sequence
  8. from kitty.cli import parse_args
  9. from kitty.cli_stub import PanelCLIOptions
  10. from kitty.constants import is_macos, kitten_exe
  11. from kitty.fast_data_types import (
  12. GLFW_EDGE_BOTTOM,
  13. GLFW_EDGE_CENTER,
  14. GLFW_EDGE_CENTER_SIZED,
  15. GLFW_EDGE_LEFT,
  16. GLFW_EDGE_NONE,
  17. GLFW_EDGE_RIGHT,
  18. GLFW_EDGE_TOP,
  19. GLFW_FOCUS_EXCLUSIVE,
  20. GLFW_FOCUS_NOT_ALLOWED,
  21. GLFW_FOCUS_ON_DEMAND,
  22. GLFW_LAYER_SHELL_BACKGROUND,
  23. GLFW_LAYER_SHELL_OVERLAY,
  24. GLFW_LAYER_SHELL_PANEL,
  25. GLFW_LAYER_SHELL_TOP,
  26. set_layer_shell_config,
  27. toggle_os_window_visibility,
  28. )
  29. from kitty.simple_cli_definitions import panel_options_spec
  30. from kitty.types import LayerShellConfig
  31. from kitty.typing_compat import BossType
  32. from kitty.utils import log_error
  33. args = PanelCLIOptions()
  34. help_text = 'Use a command line program to draw a GPU accelerated panel on your desktop'
  35. usage = '[cmdline-to-run ...]'
  36. def panel_kitten_options_spec() -> str:
  37. if not hasattr(panel_kitten_options_spec, 'ans'):
  38. setattr(panel_kitten_options_spec, 'ans', panel_options_spec())
  39. ans: str = getattr(panel_kitten_options_spec, 'ans')
  40. return ans
  41. def parse_panel_args(args: list[str]) -> tuple[PanelCLIOptions, list[str]]:
  42. return parse_args(args, panel_kitten_options_spec, usage, help_text, 'kitty +kitten panel', result_class=PanelCLIOptions)
  43. def dual_distance(spec: str, min_cell_value_if_no_pixels: int = 0) -> tuple[int, int]:
  44. with suppress(Exception):
  45. return int(spec), 0
  46. if spec.endswith('px'):
  47. return min_cell_value_if_no_pixels, int(spec[:-2])
  48. if spec.endswith('c'):
  49. return int(spec[:-1]), 0
  50. return min_cell_value_if_no_pixels, 0
  51. def layer_shell_config(opts: PanelCLIOptions) -> LayerShellConfig:
  52. ltype = {
  53. 'background': GLFW_LAYER_SHELL_BACKGROUND,
  54. 'bottom': GLFW_LAYER_SHELL_PANEL,
  55. 'top': GLFW_LAYER_SHELL_TOP,
  56. 'overlay': GLFW_LAYER_SHELL_OVERLAY
  57. }.get(opts.layer, GLFW_LAYER_SHELL_PANEL)
  58. ltype = GLFW_LAYER_SHELL_BACKGROUND if opts.edge == 'background' else ltype
  59. edge = {
  60. 'top': GLFW_EDGE_TOP, 'bottom': GLFW_EDGE_BOTTOM, 'left': GLFW_EDGE_LEFT, 'right': GLFW_EDGE_RIGHT,
  61. 'center': GLFW_EDGE_CENTER, 'none': GLFW_EDGE_NONE, 'center-sized': GLFW_EDGE_CENTER_SIZED,
  62. }.get(opts.edge, GLFW_EDGE_TOP)
  63. focus_policy = {
  64. 'not-allowed': GLFW_FOCUS_NOT_ALLOWED, 'exclusive': GLFW_FOCUS_EXCLUSIVE, 'on-demand': GLFW_FOCUS_ON_DEMAND
  65. }.get(opts.focus_policy, GLFW_FOCUS_NOT_ALLOWED)
  66. if opts.hide_on_focus_loss:
  67. focus_policy = GLFW_FOCUS_ON_DEMAND
  68. x, y = dual_distance(opts.columns, min_cell_value_if_no_pixels=1), dual_distance(opts.lines, min_cell_value_if_no_pixels=1)
  69. return LayerShellConfig(type=ltype,
  70. edge=edge,
  71. x_size_in_cells=x[0], x_size_in_pixels=x[1],
  72. y_size_in_cells=y[0], y_size_in_pixels=y[1],
  73. requested_top_margin=max(0, opts.margin_top),
  74. requested_left_margin=max(0, opts.margin_left),
  75. requested_bottom_margin=max(0, opts.margin_bottom),
  76. requested_right_margin=max(0, opts.margin_right),
  77. focus_policy=focus_policy,
  78. requested_exclusive_zone=opts.exclusive_zone,
  79. override_exclusive_zone=opts.override_exclusive_zone,
  80. hide_on_focus_loss=opts.hide_on_focus_loss,
  81. output_name=opts.output_name or '')
  82. mtime_map: dict[str, float] = {}
  83. def have_config_files_been_updated(config_files: Iterable[str]) -> bool:
  84. ans = False
  85. for cf in config_files:
  86. try:
  87. mtime = os.path.getmtime(cf)
  88. except OSError:
  89. mtime = 0
  90. if mtime_map.get(cf, 0) != mtime:
  91. ans = True
  92. mtime_map[cf] = mtime
  93. return ans
  94. def handle_single_instance_command(boss: BossType, sys_args: Sequence[str], environ: Mapping[str, str], notify_on_os_window_death: str | None = '') -> None:
  95. global args
  96. from kitty.cli import parse_override
  97. from kitty.main import run_app
  98. from kitty.tabs import SpecialWindow
  99. try:
  100. new_args, items = parse_panel_args(list(sys_args[1:]))
  101. except BaseException as e:
  102. log_error(f'Invalid arguments received over single instance socket: {sys_args} with error: {e}')
  103. return
  104. lsc = layer_shell_config(new_args)
  105. layer_shell_config_changed = lsc != run_app.layer_shell_config
  106. config_changed = have_config_files_been_updated(new_args.config) or args.config != new_args.config or args.override != new_args.override
  107. args = new_args
  108. if config_changed:
  109. boss.load_config_file(*args.config, overrides=tuple(map(parse_override, new_args.override)))
  110. if args.toggle_visibility and boss.os_window_map:
  111. for os_window_id in boss.os_window_map:
  112. toggle_os_window_visibility(os_window_id)
  113. if layer_shell_config_changed:
  114. set_layer_shell_config(os_window_id, lsc)
  115. return
  116. items = items or [kitten_exe(), 'run-shell']
  117. os_window_id = boss.add_os_panel(lsc, args.cls, args.name)
  118. if notify_on_os_window_death:
  119. boss.os_window_death_actions[os_window_id] = partial(boss.notify_on_os_window_death, notify_on_os_window_death)
  120. tm = boss.os_window_map[os_window_id]
  121. tm.new_tab(SpecialWindow(cmd=items, env=dict(environ)))
  122. def main(sys_args: list[str]) -> None:
  123. # run_kitten run using runpy.run_module which does not import into
  124. # sys.modules, which means the module will be re-imported later, causing
  125. # global variables to be duplicated, so do it now.
  126. from kittens.panel.main import actual_main
  127. actual_main(sys_args)
  128. return
  129. def actual_main(sys_args: list[str]) -> None:
  130. global args
  131. args, items = parse_panel_args(sys_args[1:])
  132. have_config_files_been_updated(args.config)
  133. sys.argv = ['kitty']
  134. if args.debug_rendering:
  135. sys.argv.append('--debug-rendering')
  136. for config in args.config:
  137. sys.argv.extend(('--config', config))
  138. if not is_macos:
  139. sys.argv.extend(('--class', args.cls))
  140. if args.name:
  141. sys.argv.extend(('--name', args.name))
  142. if args.start_as_hidden:
  143. sys.argv.append('--start-as=hidden')
  144. if args.grab_keyboard:
  145. sys.argv.append('--grab-keyboard')
  146. for override in args.override:
  147. sys.argv.extend(('--override', override))
  148. sys.argv.append('--override=linux_display_server=auto')
  149. sys.argv.append('--override=macos_quit_when_last_window_closed=yes')
  150. sys.argv.append('--override=macos_hide_from_tasks=yes')
  151. sys.argv.append('--override=macos_window_resizable=no')
  152. if args.single_instance:
  153. sys.argv.append('--single-instance')
  154. if args.instance_group:
  155. sys.argv.append(f'--instance-group={args.instance_group}')
  156. if args.listen_on:
  157. sys.argv.append(f'--listen-on={args.listen_on}')
  158. sys.argv.extend(items)
  159. from kitty.main import main as real_main
  160. from kitty.main import run_app
  161. run_app.cached_values_name = 'panel'
  162. run_app.layer_shell_config = layer_shell_config(args)
  163. real_main(called_from_panel=True)
  164. if __name__ == '__main__':
  165. main(sys.argv)
  166. elif __name__ == '__doc__':
  167. cd: dict = sys.cli_docs # type: ignore
  168. cd['usage'] = usage
  169. cd['options'] = panel_kitten_options_spec
  170. cd['help_text'] = help_text
  171. cd['short_desc'] = help_text