Wire up the codegen C cli parser
This commit is contained in:
parent
3dc85838c8
commit
b0ae88ada9
8 changed files with 314 additions and 289 deletions
|
|
@ -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
|
||||
|
|
|
|||
38
kitty/cli.py
38
kitty/cli.py
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 = []
|
||||
|
|
|
|||
|
|
@ -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
|
||||
# }}}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue