main.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. #!/usr/bin/env python
  2. # License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
  3. import sys
  4. from typing import Any, Callable, Dict, List, Tuple
  5. from kitty.cli import parse_args
  6. from kitty.cli_stub import PanelCLIOptions
  7. from kitty.constants import appname, is_macos, is_wayland
  8. from kitty.fast_data_types import (
  9. GLFW_EDGE_BOTTOM,
  10. GLFW_EDGE_LEFT,
  11. GLFW_EDGE_RIGHT,
  12. GLFW_EDGE_TOP,
  13. GLFW_LAYER_SHELL_BACKGROUND,
  14. GLFW_LAYER_SHELL_PANEL,
  15. glfw_primary_monitor_size,
  16. make_x11_window_a_dock_window,
  17. )
  18. from kitty.os_window_size import WindowSizeData, edge_spacing
  19. from kitty.types import LayerShellConfig
  20. from kitty.typing import EdgeLiteral
  21. OPTIONS = r'''
  22. --lines --columns
  23. type=int
  24. default=1
  25. The number of lines shown in the panel if horizontal otherwise the number of columns shown in the panel. Ignored for background panels.
  26. --edge
  27. choices=top,bottom,left,right,background
  28. default=top
  29. Which edge of the screen to place the panel on. Note that some window managers
  30. (such as i3) do not support placing docked windows on the left and right edges.
  31. The value :code:`background` means make the panel the "desktop wallpaper". This
  32. is only supported on Wayland, not X11 and note that when using sway if you set
  33. a background in your sway config it will cover the background drawn using this
  34. kitten.
  35. --config -c
  36. type=list
  37. Path to config file to use for kitty when drawing the panel.
  38. --override -o
  39. type=list
  40. Override individual kitty configuration options, can be specified multiple times.
  41. Syntax: :italic:`name=value`. For example: :option:`kitty +kitten panel -o` font_size=20
  42. --output-name
  43. On Wayland, the panel can only be displayed on a single monitor (output) at a time. This allows
  44. you to specify which output is used, by name. If not specified the compositor will choose an
  45. output automatically, typically the last output the user interacted with or the primary monitor.
  46. --class
  47. dest=cls
  48. default={appname}-panel
  49. condition=not is_macos
  50. Set the class part of the :italic:`WM_CLASS` window property. On Wayland, it sets the app id.
  51. --name
  52. condition=not is_macos
  53. Set the name part of the :italic:`WM_CLASS` property (defaults to using the value from :option:`{appname} --class`)
  54. --debug-rendering
  55. type=bool-set
  56. For internal debugging use.
  57. '''.format(appname=appname).format
  58. args = PanelCLIOptions()
  59. help_text = 'Use a command line program to draw a GPU accelerated panel on your X11 desktop'
  60. usage = 'program-to-run'
  61. def parse_panel_args(args: List[str]) -> Tuple[PanelCLIOptions, List[str]]:
  62. return parse_args(args, OPTIONS, usage, help_text, 'kitty +kitten panel', result_class=PanelCLIOptions)
  63. Strut = Tuple[int, int, int, int, int, int, int, int, int, int, int, int]
  64. def create_strut(
  65. win_id: int,
  66. left: int = 0, right: int = 0, top: int = 0, bottom: int = 0, left_start_y: int = 0, left_end_y: int = 0,
  67. right_start_y: int = 0, right_end_y: int = 0, top_start_x: int = 0, top_end_x: int = 0,
  68. bottom_start_x: int = 0, bottom_end_x: int = 0
  69. ) -> Strut:
  70. return left, right, top, bottom, left_start_y, left_end_y, right_start_y, right_end_y, top_start_x, top_end_x, bottom_start_x, bottom_end_x
  71. def create_top_strut(win_id: int, width: int, height: int) -> Strut:
  72. return create_strut(win_id, top=height, top_end_x=width)
  73. def create_bottom_strut(win_id: int, width: int, height: int) -> Strut:
  74. return create_strut(win_id, bottom=height, bottom_end_x=width)
  75. def create_left_strut(win_id: int, width: int, height: int) -> Strut:
  76. return create_strut(win_id, left=width, left_end_y=height)
  77. def create_right_strut(win_id: int, width: int, height: int) -> Strut:
  78. return create_strut(win_id, right=width, right_end_y=height)
  79. window_width = window_height = 0
  80. def setup_x11_window(win_id: int) -> None:
  81. if is_wayland():
  82. return
  83. func = globals()[f'create_{args.edge}_strut']
  84. strut = func(win_id, window_width, window_height)
  85. make_x11_window_a_dock_window(win_id, strut)
  86. def initial_window_size_func(opts: WindowSizeData, cached_values: Dict[str, Any]) -> Callable[[int, int, float, float, float, float], Tuple[int, int]]:
  87. def es(which: EdgeLiteral) -> float:
  88. return edge_spacing(which, opts)
  89. def initial_window_size(cell_width: int, cell_height: int, dpi_x: float, dpi_y: float, xscale: float, yscale: float) -> Tuple[int, int]:
  90. if not is_macos and not is_wayland():
  91. # Not sure what the deal with scaling on X11 is
  92. xscale = yscale = 1
  93. global window_width, window_height
  94. monitor_width, monitor_height = glfw_primary_monitor_size()
  95. if args.edge in {'top', 'bottom'}:
  96. spacing = es('top') + es('bottom')
  97. window_height = int(cell_height * args.lines / yscale + (dpi_y / 72) * spacing + 1)
  98. window_width = monitor_width
  99. elif args.edge == 'background':
  100. window_width, window_height = monitor_width, monitor_height
  101. else:
  102. spacing = es('left') + es('right')
  103. window_width = int(cell_width * args.lines / xscale + (dpi_x / 72) * spacing + 1)
  104. window_height = monitor_height
  105. return window_width, window_height
  106. return initial_window_size
  107. def layer_shell_config(opts: PanelCLIOptions) -> LayerShellConfig:
  108. ltype = GLFW_LAYER_SHELL_BACKGROUND if opts.edge == 'background' else GLFW_LAYER_SHELL_PANEL
  109. edge = {'top': GLFW_EDGE_TOP, 'bottom': GLFW_EDGE_BOTTOM, 'left': GLFW_EDGE_LEFT, 'right': GLFW_EDGE_RIGHT}.get(opts.edge, GLFW_EDGE_TOP)
  110. return LayerShellConfig(type=ltype, edge=edge, size_in_cells=max(1, opts.lines), output_name=opts.output_name or '')
  111. def main(sys_args: List[str]) -> None:
  112. global args
  113. if is_macos:
  114. raise SystemExit('Currently the panel kitten is not supported on macOS')
  115. args, items = parse_panel_args(sys_args[1:])
  116. if not items:
  117. raise SystemExit('You must specify the program to run')
  118. sys.argv = ['kitty']
  119. if args.debug_rendering:
  120. sys.argv.append('--debug-rendering')
  121. for config in args.config:
  122. sys.argv.extend(('--config', config))
  123. sys.argv.extend(('--class', args.cls))
  124. if args.name:
  125. sys.argv.extend(('--name', args.name))
  126. for override in args.override:
  127. sys.argv.extend(('--override', override))
  128. sys.argv.append('--override=linux_display_server=auto')
  129. sys.argv.extend(items)
  130. from kitty.main import main as real_main
  131. from kitty.main import run_app
  132. run_app.cached_values_name = 'panel'
  133. run_app.layer_shell_config = layer_shell_config(args)
  134. run_app.first_window_callback = setup_x11_window
  135. run_app.initial_window_size_func = initial_window_size_func
  136. real_main()
  137. if __name__ == '__main__':
  138. main(sys.argv)
  139. elif __name__ == '__doc__':
  140. cd: dict = sys.cli_docs # type: ignore
  141. cd['usage'] = usage
  142. cd['options'] = OPTIONS
  143. cd['help_text'] = help_text
  144. cd['short_desc'] = help_text