Wire up the codegen C cli parser

This commit is contained in:
Kovid Goyal 2025-04-27 22:04:56 +05:30
parent 3dc85838c8
commit b0ae88ada9
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
8 changed files with 314 additions and 289 deletions

View file

@ -9,7 +9,7 @@
from kitty.cli import parse_args
from kitty.cli_stub import PanelCLIOptions
from kitty.constants import appname, is_macos, is_wayland, kitten_exe
from kitty.constants import is_macos, is_wayland, kitten_exe
from kitty.fast_data_types import (
GLFW_EDGE_BOTTOM,
GLFW_EDGE_CENTER,
@ -29,7 +29,7 @@
toggle_os_window_visibility,
)
from kitty.os_window_size import WindowSizeData, edge_spacing
from kitty.simple_cli_definitions import listen_on_defn
from kitty.simple_cli_definitions import panel_kitten_options_spec
from kitty.types import LayerShellConfig
from kitty.typing_compat import BossType, EdgeLiteral
from kitty.utils import log_error
@ -41,171 +41,6 @@
default_quake_cmdline = f'{quake} --exclusive-zone=0 --override-exclusive-zone --detach'
default_macos_quake_cmdline = f'{quake}'
OPTIONS = r'''
--lines
default=1
The number of lines shown in the panel. Ignored for background, centered, and vertical panels.
If it has the suffix :code:`px` then it sets the height of the panel in pixels instead of lines.
--columns
default=1
The number of columns shown in the panel. Ignored for background, centered, and horizontal panels.
If it has the suffix :code:`px` then it sets the width of the panel in pixels instead of columns.
--margin-top
type=int
default=0
Set the top margin for the panel, in pixels. Has no effect for bottom edge panels.
Only works on macOS and Wayland compositors that supports the wlr layer shell protocol.
--margin-left
type=int
default=0
Set the left margin for the panel, in pixels. Has no effect for right edge panels.
Only works on macOS and Wayland compositors that supports the wlr layer shell protocol.
--margin-bottom
type=int
default=0
Set the bottom margin for the panel, in pixels. Has no effect for top edge panels.
Only works on macOS and Wayland compositors that supports the wlr layer shell protocol.
--margin-right
type=int
default=0
Set the right margin for the panel, in pixels. Has no effect for left edge panels.
Only works on macOS and Wayland compositors that supports the wlr layer shell protocol.
--edge
choices=top,bottom,left,right,background,center,none
default=top
Which edge of the screen to place the panel on. Note that some window managers
(such as i3) do not support placing docked windows on the left and right edges.
The value :code:`background` means make the panel the "desktop wallpaper". This
is not supported on X11 and note that when using sway if you set
a background in your sway config it will cover the background drawn using this
kitten.
Additionally, there are two more values: :code:`center` and :code:`none`.
The value :code:`center` anchors the panel to all sides and covers the entire
display (on macOS the part of the display not covered by titlebar and dock).
The panel can be shrunk and placed using the margin parameters.
The value :code:`none` anchors the panel to the top left corner and should be
placed and using the margin parameters. It's size is set bye :option:`--lines`
and :option:`--columns`.
--layer
choices=background,bottom,top,overlay
default=bottom
On a Wayland compositor that supports the wlr layer shell protocol, specifies the layer
on which the panel should be drawn. This parameter is ignored and set to
:code:`background` if :option:`--edge` is set to :code:`background`. On macOS, maps
these to appropriate NSWindow *levels*.
--config -c
type=list
Path to config file to use for kitty when drawing the panel.
--override -o
type=list
Override individual kitty configuration options, can be specified multiple times.
Syntax: :italic:`name=value`. For example: :option:`kitty +kitten panel -o` font_size=20
--output-name
On Wayland, the panel can only be displayed on a single monitor (output) at a time. This allows
you to specify which output is used, by name. If not specified the compositor will choose an
output automatically, typically the last output the user interacted with or the primary monitor.
--class --app-id
dest=cls
default={appname}-panel
condition=not is_macos
Set the class part of the :italic:`WM_CLASS` window property. On Wayland, it sets the app id.
--name
condition=not is_macos
Set the name part of the :italic:`WM_CLASS` property (defaults to using the value from :option:`{appname} --class`)
--focus-policy
choices=not-allowed,exclusive,on-demand
default=not-allowed
On a Wayland compositor that supports the wlr layer shell protocol, specify the focus policy for keyboard
interactivity with the panel. Please refer to the wlr layer shell protocol documentation for more details.
On macOS, :code:`exclusive` and :code:`on-demand` are currently the same. Ignored on X11.
--exclusive-zone
type=int
default=-1
On a Wayland compositor that supports the wlr layer shell protocol, request a given exclusive zone for the panel.
Please refer to the wlr layer shell documentation for more details on the meaning of exclusive and its value.
If :option:`--edge` is set to anything other than :code:`center` or :code:`none`, this flag will not have any
effect unless the flag :option:`--override-exclusive-zone` is also set.
If :option:`--edge` is set to :code:`background`, this option has no effect.
Ignored on X11 and macOS.
--override-exclusive-zone
type=bool-set
On a Wayland compositor that supports the wlr layer shell protocol, override the default exclusive zone.
This has effect only if :option:`--edge` is set to :code:`top`, :code:`left`, :code:`bottom` or :code:`right`.
Ignored on X11 and macOS.
--single-instance -1
type=bool-set
If specified only a single instance of the panel will run. New
invocations will instead create a new top-level window in the existing
panel instance.
--instance-group
Used in combination with the :option:`--single-instance` option. All
panel invocations with the same :option:`--instance-group` will result
in new panels being created in the first panel instance within that group.
{listen_on_defn}
--toggle-visibility
type=bool-set
When set and using :option:`--single-instance` will toggle the visibility of the
existing panel rather than creating a new one.
--start-as-hidden
type=bool-set
Start in hidden mode, useful with :option:`--toggle-visibility`.
--detach
type=bool-set
Detach from the controlling terminal, if any, running in an independent child process,
the parent process exits immediately.
--detached-log
Path to a log file to store STDOUT/STDERR when using :option:`--detach`
--debug-rendering
type=bool-set
For internal debugging use.
'''.format(appname=appname, listen_on_defn=listen_on_defn).format
args = PanelCLIOptions()
help_text = 'Use a command line program to draw a GPU accelerated panel on your desktop'
@ -213,7 +48,7 @@
def parse_panel_args(args: list[str]) -> tuple[PanelCLIOptions, list[str]]:
return parse_args(args, OPTIONS, usage, help_text, 'kitty +kitten panel', result_class=PanelCLIOptions)
return parse_args(args, panel_kitten_options_spec, usage, help_text, 'kitty +kitten panel', result_class=PanelCLIOptions)
Strut = tuple[int, int, int, int, int, int, int, int, int, int, int, int]
@ -396,6 +231,6 @@ def main(sys_args: list[str]) -> None:
elif __name__ == '__doc__':
cd: dict = sys.cli_docs # type: ignore
cd['usage'] = usage
cd['options'] = OPTIONS
cd['options'] = panel_kitten_options_spec
cd['help_text'] = help_text
cd['short_desc'] = help_text

View file

@ -580,6 +580,19 @@ def handle_version(self) -> NoReturn:
raise SystemExit(0)
PreparsedCLIFlags = tuple[dict[str, tuple[Any, bool]], list[str]]
def apply_preparsed_cli_flags(preparsed_from_c: PreparsedCLIFlags, ans: Any, create_oc: Callable[[], Options]) -> list[str]:
for key, (val, is_seen) in preparsed_from_c[0].items():
if key == 'help' and is_seen and val:
create_oc().handle_help()
elif key == 'version' and is_seen and val:
create_oc().handle_version()
setattr(ans, key, val)
return preparsed_from_c[1]
def parse_cmdline(oc: Options, disabled: OptionSpecSeq, ans: Any, args: list[str] | None = None) -> list[str]:
names_map = oc.names_map.copy()
values_map = oc.values_map.copy()
@ -590,16 +603,11 @@ def parse_cmdline(oc: Options, disabled: OptionSpecSeq, ans: Any, args: list[str
names_map['version'] = {'type': 'bool-set', 'aliases': ('--version', '-v')} # type: ignore
values_map['version'] = False
try:
vals, leftover_args = parse_cli_from_spec(sys.argv[1:] if args is None else args, names_map, values_map)
preparsed = parse_cli_from_spec(sys.argv[1:] if args is None else args, names_map, values_map)
except Exception as e:
raise SystemExit(str(e))
leftover_args = apply_preparsed_cli_flags(preparsed, ans, lambda: oc)
for key, (val, is_seen) in vals.items():
if key == 'help' and is_seen and val:
oc.handle_help()
elif key == 'version' and is_seen and val:
oc.handle_version()
setattr(ans, key, val)
for opt in disabled:
if not isinstance(opt, str):
setattr(ans, opt['dest'], defval_for_opt(opt))
@ -634,14 +642,24 @@ def parse_args(
message: str | None = None,
appname: str | None = None,
result_class: type[T] | None = None,
preparsed_from_c: PreparsedCLIFlags | None = None,
) -> tuple[T, list[str]]:
options = parse_option_spec(ospec())
seq, disabled = options
oc = Options(seq, usage, message, appname)
if result_class is not None:
ans = result_class()
else:
ans = cast(T, CLIOptions())
def create_oc() -> Options:
options = parse_option_spec(ospec())
seq, disabled = options
return Options(seq, usage, message, appname)
if preparsed_from_c:
return ans, apply_preparsed_cli_flags(preparsed_from_c, ans, create_oc)
options = parse_option_spec(ospec())
seq, disabled = options
oc = Options(seq, usage, message, appname)
return ans, parse_cmdline(oc, disabled, ans, args=args)

View file

@ -231,12 +231,12 @@ dealloc_cli_spec(void *v) {
#define RAII_CLISpec(name) __attribute__((cleanup(dealloc_cli_spec))) CLISpec name = {0}; alloc_cli_spec(&name)
static bool
parse_cli_loop(CLISpec *spec, bool save_original_argv, int argc, char **argv) { // argv must contain arg1 and beyond
parse_cli_loop(CLISpec *spec, bool save_original_argv, int argc, char **argv) {
enum { NORMAL, EXPECTING_ARG } state = NORMAL;
spec->argc = 0; spec->argv = NULL; spec->errmsg = NULL; spec->original_argc = argc; spec->original_argv = NULL;
if (save_original_argv) {
char **copy = alloc_for_cli(spec, sizeof(char*) * (argc + 1));
if (!spec->original_argv) OOM;
if (!copy) OOM;
copy[argc] = NULL;
for (int i = 0; i < argc; i++) {
size_t len = strlen(argv[i]);
@ -249,7 +249,7 @@ parse_cli_loop(CLISpec *spec, bool save_original_argv, int argc, char **argv) {
}
char flag[3] = {'-', 0, 0};
const char *current_option = NULL;
for (int i = 0; i < argc; i++) {
for (int i = 1; i < argc; i++) {
char *arg = argv[i];
switch(state) {
case NORMAL: {
@ -383,16 +383,17 @@ parse_cli_from_python_spec(PyObject *self, PyObject *args) {
if (!PyArg_ParseTuple(args, "O!O!O!", &PyList_Type, &pyargs, &PyDict_Type, &names_map, &PyDict_Type, &defval_map)) return NULL;
int argc = PyList_GET_SIZE(pyargs);
RAII_CLISpec(spec);
char **argv = alloc_for_cli(&spec, sizeof(char*) * (argc + 1));
char **argv = alloc_for_cli(&spec, sizeof(char*) * (argc + 2));
if (!argv) return PyErr_NoMemory();
argv[0] = "parse_cli_from_python_spec";
for (int i = 0; i < argc; i++) {
Py_ssize_t sz;
const char *src = PyUnicode_AsUTF8AndSize(PyList_GET_ITEM(pyargs, i), &sz);
argv[i] = alloc_for_cli(&spec, sz);
if (!argv[i]) return PyErr_NoMemory();
memcpy(argv[i], src, sz);
argv[i + 1] = alloc_for_cli(&spec, sz);
if (!argv[i + 1]) return PyErr_NoMemory();
memcpy(argv[i + 1], src, sz);
}
argv[argc] = 0;
argv[++argc] = 0;
PyObject *key = NULL, *opt = NULL;
Py_ssize_t pos = 0;
while (PyDict_Next(names_map, &pos, &key, &opt)) {

View file

@ -11,8 +11,8 @@
#include <stddef.h>
typedef struct CLIOptions {
const char *session, *instance_group, *detached_log;
bool single_instance, version_requested, wait_for_single_instance_window_close, detach;
const char *session, *instance_group;
bool wait_for_single_instance_window_close;
int open_url_count; char **open_urls;
} CLIOptions;

View file

@ -13,11 +13,14 @@
#include <mach-o/dyld.h>
#include <sys/syslimits.h>
#include <sys/stat.h>
#include <os/log.h>
#else
#include <limits.h>
#endif
#include "launcher.h"
#include "utils.h"
#define FOR_LAUNCHER
#include "cli-parser-data_generated.h"
#ifndef KITTY_LIB_PATH
#define KITTY_LIB_PATH "../.."
@ -42,8 +45,7 @@ safe_realpath(const char* src, char *buf, size_t buf_sz) {
typedef struct {
const char *exe, *exe_dir, *lc_ctype, *lib_dir, *config_dir;
char **argv;
int argc;
CLISpec *cli_spec;
bool launched_by_launch_services, is_quick_access_terminal;
} RunData;
@ -83,6 +85,12 @@ set_kitty_run_data(RunData *run_data, bool from_source, wchar_t *extensions_dir)
if (!cdir) { PyErr_Print(); return false; }
S(config_dir, cdir);
}
PyObject *cli_flags = cli_parse_result_as_python(run_data->cli_spec);
if (!cli_flags) {
if (PyErr_Occurred()) PyErr_Print();
return false;
}
S(cli_flags, cli_flags);
#undef S
int ret = PySys_SetObject("kitty_run_data", ans);
@ -120,7 +128,7 @@ run_embedded(RunData *run_data) {
wchar_t python_home[PATH_MAX];
canonicalize_path_wide(python_home_full, python_home, PATH_MAX);
bypy_initialize_interpreter(
L"kitty", python_home, L"kitty_main", extensions_dir, run_data->argc, run_data->argv);
L"kitty", python_home, L"kitty_main", extensions_dir, run_data->cli_spec->original_argc, run_data->cli_spec->original_argv);
if (!set_kitty_run_data(run_data, false, extensions_dir)) return 1;
set_sys_bool("frozen", true);
return bypy_run_interpreter();
@ -148,7 +156,7 @@ run_embedded(RunData *run_data) {
PyConfig_InitPythonConfig(&config);
config.parse_argv = 0;
config.optimization_level = 2;
status = PyConfig_SetBytesArgv(&config, run_data->argc, run_data->argv);
status = PyConfig_SetBytesArgv(&config, run_data->cli_spec->original_argc, run_data->cli_spec->original_argv);
if (PyStatus_Exception(status)) goto fail;
status = PyConfig_SetBytesString(&config, &config.executable, run_data->exe);
if (PyStatus_Exception(status)) goto fail;
@ -307,87 +315,53 @@ exec_kitten(int argc, char *argv[], char *exe_dir) {
}
static bool
is_boolean_flag(const char *x) {
static const char *all_boolean_options = "";
char buf[128];
safe_snprintf(buf, sizeof(buf), " %s ", x);
return strstr(all_boolean_options, buf) != NULL;
parse_and_check_kitty_cli(CLISpec *cli_spec, int argc, char **argv) {
parse_cli_for_kitty(cli_spec, argc, argv);
if (cli_spec->errmsg) {
fprintf(stderr, "%s\n", cli_spec->errmsg);
#ifdef __APPLE__
os_log_error(OS_LOG_DEFAULT, "%{public}s", cli_spec->errmsg);
#endif
return false;
}
return true;
}
static void
handle_fast_commandline(int argc, char *argv[], const char *instance_group_prefix) {
char current_option_expecting_argument[128] = {0};
CLIOptions opts = {0};
int first_arg = 1;
if (argc > 1 && strcmp(argv[1], "+open") == 0) {
first_arg = 2;
} else if (argc > 2 && strcmp(argv[1], "+") == 0 && strcmp(argv[2], "open") == 0) {
first_arg = 3;
}
for (int i = first_arg; i < argc; i++) {
const char *arg = argv[i];
if (current_option_expecting_argument[0]) {
handle_option_value:
if (strcmp(current_option_expecting_argument, "session") == 0) {
opts.session = arg;
} else if (strcmp(current_option_expecting_argument, "instance-group") == 0) {
opts.instance_group = arg;
} else if (strcmp(current_option_expecting_argument, "detached-log") == 0) {
opts.detached_log = arg;
}
current_option_expecting_argument[0] = 0;
} else {
if (!arg || !arg[0] || !arg[1] || arg[0] != '-' || strcmp(arg, "--") == 0) {
if (first_arg > 1) {
opts.open_urls = argv + i;
opts.open_url_count = argc - i;
}
break;
}
if (arg[1] == '-') { // long opt
const char *equal = strchr(arg, '=');
const char *q = arg + 2;
if (equal == NULL) {
if (strcmp(q, "version") == 0) {
opts.version_requested = true;
} else if (strcmp(q, "single-instance") == 0) {
opts.single_instance = true;
} else if (strcmp(q, "wait-for-single-instance-window-close") == 0) {
opts.wait_for_single_instance_window_close = true;
} else if (strcmp(q, "detach") == 0) {
opts.detach = true;
} else if (strcmp(q, "help") == 0) {
return;
} else if (!is_boolean_flag(arg+2)) {
strncpy(current_option_expecting_argument, q, sizeof(current_option_expecting_argument)-1);
}
} else {
memcpy(current_option_expecting_argument, q, equal - q);
current_option_expecting_argument[equal - q] = 0;
arg = equal + 1;
goto handle_option_value;
}
} else {
char buf[2] = {0};
current_option_expecting_argument[0] = 0;
for (int i = 1; arg[i] != 0; i++) {
switch(arg[i]) {
case '=':
arg = arg + i + 1;
goto handle_option_value;
case 'v': opts.version_requested = true; break;
case '1': opts.single_instance = true; break;
case 'h': return;
default:
buf[0] = arg[i]; buf[1] = 0;
if (!is_boolean_flag(buf)) { current_option_expecting_argument[0] = arg[i]; current_option_expecting_argument[1] = 0; }
}
}
}
}
static bool
parse_and_check_panel_kitten_cli(CLISpec *cli_spec, int argc, char **argv) {
parse_cli_for_panel_kitten(cli_spec, argc, argv);
if (cli_spec->errmsg) {
fprintf(stderr, "%s\n", cli_spec->errmsg);
#ifdef __APPLE__
os_log_error(OS_LOG_DEFAULT, "%{public}s", cli_spec->errmsg);
#endif
return false;
}
return true;
}
if (opts.version_requested) {
static void
handle_fast_commandline(CLISpec *cli_spec, const char *instance_group_prefix, int offset_for_panel_kitten) {
CLIOptions opts = {0};
RAII_CLISpec(subcommand_cli_spec);
if (offset_for_panel_kitten < 0) {
// Look for +open
int offset = 0;
if (cli_spec->original_argc > 1 && strcmp("+open", cli_spec->original_argv[1]) == 0) offset = 1;
else if (cli_spec->original_argc > 2 && strcmp("+", cli_spec->original_argv[1]) == 0 && strcmp("open", cli_spec->original_argv[2]) == 0) offset = 2;
if (offset) {
if (!parse_and_check_kitty_cli(&subcommand_cli_spec, cli_spec->original_argc - offset, cli_spec->original_argv + offset)) exit(1);
cli_spec = &subcommand_cli_spec;
opts.open_url_count = cli_spec->argc;
opts.open_urls = cli_spec->argv;
}
} else if (offset_for_panel_kitten > 0) {
parse_and_check_panel_kitten_cli(&subcommand_cli_spec, cli_spec->original_argc - offset_for_panel_kitten, cli_spec->original_argv + offset_for_panel_kitten);
cli_spec = &subcommand_cli_spec;
}
if (get_bool_cli_val(cli_spec, "help")) return;
if (get_bool_cli_val(cli_spec, "version")) {
if (isatty(STDOUT_FILENO)) {
printf("\x1b[3mkitty\x1b[23m \x1b[32m%s\x1b[39m created by \x1b[1;34mKovid Goyal\x1b[22;39m\n", KITTY_VERSION);
} else {
@ -395,21 +369,25 @@ handle_option_value:
}
exit(0);
}
if (opts.detach) {
opts.session = get_string_cli_val(cli_spec, "session");
if (get_bool_cli_val(cli_spec, "detach")) {
#define reopen_or_fail(path, mode, which) { errno = 0; if (freopen(path, mode, which) == NULL) { int s = errno; fprintf(stderr, "Failed to redirect %s to %s with error: ", #which, path); errno = s; perror(NULL); exit(1); } }
if (!(opts.session && ((opts.session[0] == '-' && opts.session[1] == 0) || strcmp(opts.session, "/dev/stdin") == 0))
) reopen_or_fail("/dev/null", "rb", stdin);
if (!opts.detached_log || !opts.detached_log[0]) opts.detached_log = "/dev/null";
reopen_or_fail(opts.detached_log, "ab", stdout);
reopen_or_fail(opts.detached_log, "ab", stderr);
if (!(opts.session && ((opts.session[0] == '-' && opts.session[1] == 0) || strcmp(opts.session, "/dev/stdin") == 0)))
reopen_or_fail("/dev/null", "rb", stdin);
const char *detached_log = get_string_cli_val(cli_spec, "detached_log");
if (!detached_log || !detached_log[0]) detached_log = "/dev/null";
reopen_or_fail(detached_log, "ab", stdout);
reopen_or_fail(detached_log, "ab", stderr);
#undef reopen_or_fail
if (fork() != 0) exit(0);
setsid();
}
unsetenv("KITTY_SI_DATA");
if (opts.single_instance) {
if (get_bool_cli_val(cli_spec, "single_instance")) {
char igbuf[256];
opts.wait_for_single_instance_window_close = get_bool_cli_val(cli_spec, "wait_for_single_instance_window_close");
if (instance_group_prefix && instance_group_prefix[0]) {
opts.instance_group = get_string_cli_val(cli_spec, "instance_group");
if (opts.instance_group && opts.instance_group[0]) {
safe_snprintf(igbuf, sizeof(igbuf), "%s-%s", instance_group_prefix, opts.instance_group ? opts.instance_group : "");
opts.instance_group = igbuf;
@ -417,24 +395,25 @@ handle_option_value:
opts.instance_group = instance_group_prefix;
}
}
single_instance_main(argc, argv, &opts);
single_instance_main(cli_spec->original_argc, cli_spec->original_argv, &opts);
}
}
static bool
delegate_to_kitten_if_possible(int argc, char *argv[], char* exe_dir) {
delegate_to_kitten_if_possible(CLISpec *cli_spec, char* exe_dir) {
int argc = cli_spec->original_argc; char **argv = cli_spec->original_argv;
if (argc > 1 && argv[1][0] == '@') exec_kitten(argc, argv, exe_dir);
if (argc > 2 && strcmp(argv[1], "+kitten") == 0) {
if (is_wrapped_kitten(argv[2])) exec_kitten(argc - 1, argv + 1, exe_dir);
if (strcmp(argv[2], "panel") == 0) {
handle_fast_commandline(argc - 2, argv + 2, "panel");
handle_fast_commandline(cli_spec, "panel", 2);
return true;
}
}
if (argc > 3 && strcmp(argv[1], "+") == 0 && strcmp(argv[2], "kitten") == 0) {
if (is_wrapped_kitten(argv[3])) exec_kitten(argc - 2, argv + 2, exe_dir);
if (strcmp(argv[3], "panel") == 0) {
handle_fast_commandline(argc - 3, argv + 3, "panel");
if (strcmp(argv[2], "panel") == 0) {
handle_fast_commandline(cli_spec, "panel", 3);
return true;
}
}
@ -483,7 +462,19 @@ int main(int argc_, char *argv_[], char* envp[]) {
(void)endswith;
#endif
(void)read_full_file;
if (!delegate_to_kitten_if_possible(argva.count, argva.argv, exe_dir)) handle_fast_commandline(argva.count, argva.argv, NULL);
RAII_CLISpec(cli_spec);
bool ok = parse_and_check_kitty_cli(&cli_spec, argva.count, argva.argv);
free_argv_array(&argva);
if (!ok) return 1;
if (cli_spec.errmsg) {
fprintf(stderr, "%s\n", cli_spec.errmsg);
#ifdef __APPLE__
os_log_error(OS_LOG_DEFAULT, "%{public}s", cli_spec.errmsg);
#endif
return 1;
}
if (!delegate_to_kitten_if_possible(&cli_spec, exe_dir)) handle_fast_commandline(&cli_spec, NULL, -1);
int ret=0;
char lib[PATH_MAX+1] = {0};
if (KITTY_LIB_PATH[0] == '/') {
@ -492,11 +483,10 @@ int main(int argc_, char *argv_[], char* envp[]) {
safe_snprintf(lib, PATH_MAX, "%s/%s", exe_dir, KITTY_LIB_PATH);
}
RunData run_data = {
.exe = exe, .exe_dir = exe_dir, .lib_dir = lib, .argc = argva.count, .argv = argva.argv, .lc_ctype = lc_ctype,
.exe = exe, .exe_dir = exe_dir, .lib_dir = lib, .cli_spec = &cli_spec, .lc_ctype = lc_ctype,
.launched_by_launch_services=launched_by_launch_services, .config_dir = config_dir, .is_quick_access_terminal=is_quick_access_terminal,
};
ret = run_embedded(&run_data);
free_argv_array(&argva);
single_instance_main(-1, NULL, NULL);
Py_FinalizeEx();
return ret;

View file

@ -472,7 +472,8 @@ def kitty_main() -> None:
'\n\nAll the normal kitty options can be used.')
else:
usage = msg = appname = None
cli_opts, rest = parse_args(args=args, result_class=CLIOptions, usage=usage, message=msg, appname=appname)
cli_flags = getattr(sys, 'kitty_run_data', {}).get('cli_flags', None)
cli_opts, rest = parse_args(args=args, result_class=CLIOptions, usage=usage, message=msg, appname=appname, preparsed_from_c=cli_flags)
if getattr(sys, 'cmdline_args_for_open', False):
setattr(sys, 'cmdline_args_for_open', rest)
cli_opts.args = []

View file

@ -302,8 +302,12 @@ def generate_c_parsers() -> Iterator[str]:
yield '#include "cli-parser.h"'
yield from generate_c_parser_for('kitty', kitty_options_spec())
yield ''
yield ''
yield from generate_c_parser_for('panel_kitten', panel_kitten_options_spec())
yield ''
# kitty CLI spec {{{
listen_on_defn = f'''\
--listen-on
completion=type:special group:complete_kitty_listen_on
@ -516,3 +520,179 @@ def kitty_options_spec() -> str:
))
ans: str = getattr(kitty_options_spec, 'ans')
return ans
# }}}
# panel CLI spec {{{
def panel_kitten_options_spec() -> str:
if not hasattr(panel_kitten_options_spec, 'ans'):
OPTIONS = r'''
--lines
default=1
The number of lines shown in the panel. Ignored for background, centered, and vertical panels.
If it has the suffix :code:`px` then it sets the height of the panel in pixels instead of lines.
--columns
default=1
The number of columns shown in the panel. Ignored for background, centered, and horizontal panels.
If it has the suffix :code:`px` then it sets the width of the panel in pixels instead of columns.
--margin-top
type=int
default=0
Set the top margin for the panel, in pixels. Has no effect for bottom edge panels.
Only works on macOS and Wayland compositors that supports the wlr layer shell protocol.
--margin-left
type=int
default=0
Set the left margin for the panel, in pixels. Has no effect for right edge panels.
Only works on macOS and Wayland compositors that supports the wlr layer shell protocol.
--margin-bottom
type=int
default=0
Set the bottom margin for the panel, in pixels. Has no effect for top edge panels.
Only works on macOS and Wayland compositors that supports the wlr layer shell protocol.
--margin-right
type=int
default=0
Set the right margin for the panel, in pixels. Has no effect for left edge panels.
Only works on macOS and Wayland compositors that supports the wlr layer shell protocol.
--edge
choices=top,bottom,left,right,background,center,none
default=top
Which edge of the screen to place the panel on. Note that some window managers
(such as i3) do not support placing docked windows on the left and right edges.
The value :code:`background` means make the panel the "desktop wallpaper". This
is not supported on X11 and note that when using sway if you set
a background in your sway config it will cover the background drawn using this
kitten.
Additionally, there are two more values: :code:`center` and :code:`none`.
The value :code:`center` anchors the panel to all sides and covers the entire
display (on macOS the part of the display not covered by titlebar and dock).
The panel can be shrunk and placed using the margin parameters.
The value :code:`none` anchors the panel to the top left corner and should be
placed and using the margin parameters. It's size is set bye :option:`--lines`
and :option:`--columns`.
--layer
choices=background,bottom,top,overlay
default=bottom
On a Wayland compositor that supports the wlr layer shell protocol, specifies the layer
on which the panel should be drawn. This parameter is ignored and set to
:code:`background` if :option:`--edge` is set to :code:`background`. On macOS, maps
these to appropriate NSWindow *levels*.
--config -c
type=list
Path to config file to use for kitty when drawing the panel.
--override -o
type=list
Override individual kitty configuration options, can be specified multiple times.
Syntax: :italic:`name=value`. For example: :option:`kitty +kitten panel -o` font_size=20
--output-name
On Wayland, the panel can only be displayed on a single monitor (output) at a time. This allows
you to specify which output is used, by name. If not specified the compositor will choose an
output automatically, typically the last output the user interacted with or the primary monitor.
--class --app-id
dest=cls
default={appname}-panel
condition=not is_macos
Set the class part of the :italic:`WM_CLASS` window property. On Wayland, it sets the app id.
--name
condition=not is_macos
Set the name part of the :italic:`WM_CLASS` property (defaults to using the value from :option:`{appname} --class`)
--focus-policy
choices=not-allowed,exclusive,on-demand
default=not-allowed
On a Wayland compositor that supports the wlr layer shell protocol, specify the focus policy for keyboard
interactivity with the panel. Please refer to the wlr layer shell protocol documentation for more details.
On macOS, :code:`exclusive` and :code:`on-demand` are currently the same. Ignored on X11.
--exclusive-zone
type=int
default=-1
On a Wayland compositor that supports the wlr layer shell protocol, request a given exclusive zone for the panel.
Please refer to the wlr layer shell documentation for more details on the meaning of exclusive and its value.
If :option:`--edge` is set to anything other than :code:`center` or :code:`none`, this flag will not have any
effect unless the flag :option:`--override-exclusive-zone` is also set.
If :option:`--edge` is set to :code:`background`, this option has no effect.
Ignored on X11 and macOS.
--override-exclusive-zone
type=bool-set
On a Wayland compositor that supports the wlr layer shell protocol, override the default exclusive zone.
This has effect only if :option:`--edge` is set to :code:`top`, :code:`left`, :code:`bottom` or :code:`right`.
Ignored on X11 and macOS.
--single-instance -1
type=bool-set
If specified only a single instance of the panel will run. New
invocations will instead create a new top-level window in the existing
panel instance.
--instance-group
Used in combination with the :option:`--single-instance` option. All
panel invocations with the same :option:`--instance-group` will result
in new panels being created in the first panel instance within that group.
{listen_on_defn}
--toggle-visibility
type=bool-set
When set and using :option:`--single-instance` will toggle the visibility of the
existing panel rather than creating a new one.
--start-as-hidden
type=bool-set
Start in hidden mode, useful with :option:`--toggle-visibility`.
--detach
type=bool-set
Detach from the controlling terminal, if any, running in an independent child process,
the parent process exits immediately.
--detached-log
Path to a log file to store STDOUT/STDERR when using :option:`--detach`
--debug-rendering
type=bool-set
For internal debugging use.
'''
setattr(panel_kitten_options_spec, 'ans', OPTIONS.format(
appname=appname, listen_on_defn=listen_on_defn,
))
ans: str = getattr(panel_kitten_options_spec, 'ans')
return ans
# }}}

View file

@ -93,9 +93,9 @@ def t(args, leftover=(), fails=False, version_called=False, help_called=False, *
self.assertEqual(tuple(leftover), tuple(actual_leftover), f'{args}\n{ans}')
for dest, defval in oc.values_map.items():
val = expected.get(dest, defval)
self.assertEqual(val, getattr(ans, dest, BaseTest), f'Failed to parse {dest} correctly for: {args} \n{ans}')
self.assertEqual(version_called, oc.version_called)
self.assertEqual(help_called, oc.help_called)
self.assertEqual(val, getattr(ans, dest, BaseTest), f'Failed to parse {dest} correctly for: {args}\n{ans}')
self.assertEqual(version_called, oc.version_called, f'Failed to call version for: {args}\n{ans}')
self.assertEqual(help_called, oc.help_called, f'Failed to call help for: {args}\n{ans}')
t('-1', bool_set=True)
t('-01', bool_reset=False, bool_set=True)