diff --git a/__main__.py b/__main__.py index a99c18c87..362f3902c 100644 --- a/__main__.py +++ b/__main__.py @@ -42,6 +42,13 @@ def complete(args: List[str]) -> None: complete_main(args[1:], entry_points, namespaced_entry_points) +def open_urls(args: List[str]) -> None: + setattr(sys, 'cmdline_args_for_open', True) + sys.argv = ['kitty'] + args[1:] + from kitty.main import main as kitty_main + kitty_main() + + def launch(args: List[str]) -> None: import runpy sys.argv = args[1:] @@ -129,6 +136,7 @@ namespaced_entry_points['hold'] = hold namespaced_entry_points['complete'] = complete namespaced_entry_points['runpy'] = runpy namespaced_entry_points['launch'] = launch +namespaced_entry_points['open'] = open_urls namespaced_entry_points['kitten'] = run_kitten namespaced_entry_points['edit-config'] = edit_config_file namespaced_entry_points['shebang'] = shebang diff --git a/docs/open_actions.rst b/docs/open_actions.rst index 8f8b12953..f2a4f12a6 100644 --- a/docs/open_actions.rst +++ b/docs/open_actions.rst @@ -114,6 +114,12 @@ URLs onto the kitty dock icon to open them with kitty. The default actions are: * Run shell scripts in a shell * Open SSH urls using the ssh command +These actions can also be executed from the command line by running:: + + open -a kitty.app file_or_url ... (on macOS only) + or + kitty +open file_or_url ... + You can customize these actions by creating a :file:`launch-actions.conf` file in the kitty config directory, just like the :file:`open-actions.conf` file above. For example: diff --git a/kitty/boss.py b/kitty/boss.py index c9d611ce9..58efe8da7 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -5,12 +5,13 @@ import atexit import json import os import re +import sys from contextlib import suppress from functools import partial from gettext import gettext as _ from typing import ( - Any, Callable, Container, Dict, Iterable, Iterator, List, Optional, Tuple, - Union + Any, Callable, Container, Dict, Iterable, Iterator, List, Optional, + Sequence, Tuple, Union ) from weakref import WeakValueDictionary @@ -273,8 +274,8 @@ class Boss: for sc in self.global_shortcuts.values(): self.keymap.pop(sc, None) - def startup_first_child(self, os_window_id: Optional[int]) -> None: - startup_sessions = create_sessions(get_options(), self.args, default_session=get_options().startup_session) + def startup_first_child(self, os_window_id: Optional[int], startup_sessions: Sequence[Optional[Session]] = ()) -> None: + startup_sessions = startup_sessions or create_sessions(get_options(), self.args, default_session=get_options().startup_session) for startup_session in startup_sessions: self.add_os_window(startup_session, os_window_id=os_window_id) os_window_id = None @@ -546,6 +547,10 @@ class Boss: from .cli_stub import CLIOptions startup_id = data.get('startup_id') args, rest = parse_args(data['args'][1:], result_class=CLIOptions) + cmdline_args_for_open = data.get('cmdline_args_for_open') + if cmdline_args_for_open: + self.launch_urls(*cmdline_args_for_open, no_replace_window=True) + return None args.args = rest opts = create_opts(args) if args.session == '-': @@ -746,7 +751,14 @@ class Boss: if not getattr(self, 'io_thread_started', False): self.child_monitor.start() self.io_thread_started = True - self.startup_first_child(first_os_window_id) + urls: List[str] = getattr(sys, 'cmdline_args_for_open', []) + sess = create_sessions(get_options(), self.args, special_window=SpecialWindow([kitty_exe(), '+runpy', 'input()'])) + if urls: + delattr(sys, 'cmdline_args_for_open') + self.startup_first_child(first_os_window_id, startup_sessions=tuple(sess)) + self.launch_urls(*urls) + else: + self.startup_first_child(first_os_window_id) if get_options().update_check_interval > 0 and not hasattr(self, 'update_check_started'): from .update_check import run_update_check @@ -2234,19 +2246,21 @@ class Boss: output = '\n'.join(f'{k}={v}' for k, v in os.environ.items()) self.display_scrollback(w, output, title=_('Current kitty env vars'), report_cursor=False) - def launch_url(self, url: str) -> None: - if url == ":cocoa::application launched::": + def launch_urls(self, *urls: str, no_replace_window: bool = False) -> None: + if urls == (":cocoa::application launched::",): self.cocoa_application_launched = True return - from .open_actions import actions_for_launch from .launch import force_window_launch - actions = list(actions_for_launch(url)) + from .open_actions import actions_for_launch + actions = [] + for url in urls: + actions.extend(actions_for_launch(url)) tab = self.active_tab if tab is not None: w = tab.active_window else: w = None - needs_window_replaced = not self.cocoa_application_launched or not self.os_window_map and w is not None and w.id == 1 + needs_window_replaced = not no_replace_window and (not self.cocoa_application_launched or not self.os_window_map) and w is not None and w.id == 1 def clear_initial_window() -> None: if needs_window_replaced and tab is not None and w is not None: @@ -2254,7 +2268,7 @@ class Boss: if not actions: with force_window_launch(needs_window_replaced): - self.launch(kitty_exe(), '+runpy', f'print("The url:", {url!r}, "is of unknown type, cannot open it.");' + self.launch(kitty_exe(), '+runpy', f'print("The url:", {urls[0]!r}, "is of unknown type, cannot open it.");' 'from kitty.utils import hold_till_enter; hold_till_enter(); raise SystemExit(1)') clear_initial_window() else: diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index 396181b6e..03fd194b0 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -1050,7 +1050,7 @@ process_cocoa_pending_actions(void) { if (cocoa_pending_actions_data.open_urls_count) { for (unsigned cpa = 0; cpa < cocoa_pending_actions_data.open_urls_count; cpa++) { if (cocoa_pending_actions_data.open_urls[cpa]) { - call_boss(launch_url, "s", cocoa_pending_actions_data.open_urls[cpa]); + call_boss(launch_urls, "s", cocoa_pending_actions_data.open_urls[cpa]); free(cocoa_pending_actions_data.open_urls[cpa]); cocoa_pending_actions_data.open_urls[cpa] = NULL; } diff --git a/kitty/main.py b/kitty/main.py index 9edc91859..5bbeba177 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -60,7 +60,7 @@ def talk_to_instance(args: CLIOptions) -> None: stdin = '' if args.session == '-': stdin = sys.stdin.read() - data = {'cmd': 'new_instance', 'args': tuple(sys.argv), + data = {'cmd': 'new_instance', 'args': tuple(sys.argv), 'cmdline_args_for_open': getattr(sys, 'cmdline_args_for_open', []), 'startup_id': os.environ.get('DESKTOP_STARTUP_ID'), 'cwd': os.getcwd(), 'stdin': stdin} notify_socket = None @@ -367,8 +367,21 @@ def _main() -> None: cwd_ok = False if not cwd_ok: os.chdir(os.path.expanduser('~')) - cli_opts, rest = parse_args(args=args, result_class=CLIOptions) - cli_opts.args = rest + if getattr(sys, 'cmdline_args_for_open', False): + usage = 'file_or_url ...' + appname = 'kitty +open' + msg = ( + 'Run kitty and open the specified files or URLs in it, using launch-actions.conf. For details' + ' see https://sw.kovidgoyal.net/kitty/open_actions/#scripting-the-opening-of-files-with-kitty-on-macos' + '\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) + if getattr(sys, 'cmdline_args_for_open', False): + setattr(sys, 'cmdline_args_for_open', rest) + cli_opts.args = [] + else: + cli_opts.args = rest if cli_opts.detach: if cli_opts.session == '-': from .session import PreReadSession