Merge branch 'feat-macos-open-url' of https://github.com/page-down/kitty

This commit is contained in:
Kovid Goyal 2022-02-05 12:46:39 +05:30
commit c0ea127810
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
14 changed files with 155 additions and 71 deletions

View File

@ -358,32 +358,49 @@ static GLFWapplicationwillfinishlaunchingfun finish_launching_callback = NULL;
finish_launching_callback();
}
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename {
(void)theApplication;
if (!filename || !_glfw.ns.file_open_callback) return NO;
const char *path = NULL;
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
(void)sender;
if (!filename || !_glfw.ns.url_open_callback) return NO;
const char *url = NULL;
@try {
path = [[NSFileManager defaultManager] fileSystemRepresentationWithPath: filename];
url = [[[NSURL fileURLWithPath:filename] absoluteString] UTF8String];
} @catch(NSException *exc) {
NSLog(@"Converting openFile filename: %@ failed with error: %@", filename, exc.reason);
return NO;
}
if (!path) return NO;
return _glfw.ns.file_open_callback(path);
if (!url) return NO;
return _glfw.ns.url_open_callback(url);
}
- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames {
(void)sender;
if (!_glfw.ns.file_open_callback || !filenames) return;
if (!_glfw.ns.url_open_callback || !filenames) return;
for (id x in filenames) {
NSString *filename = x;
const char *path = NULL;
const char *url = NULL;
@try {
path = [[NSFileManager defaultManager] fileSystemRepresentationWithPath: filename];
url = [[[NSURL fileURLWithPath:filename] absoluteString] UTF8String];
} @catch(NSException *exc) {
NSLog(@"Converting openFiles filename: %@ failed with error: %@", filename, exc.reason);
}
if (path) _glfw.ns.file_open_callback(path);
if (url) _glfw.ns.url_open_callback(url);
}
}
// Remove openFile and openFiles when the minimum supported macOS version is 10.13
- (void)application:(NSApplication *)sender openURLs:(NSArray<NSURL *> *)urls
{
(void)sender;
if (!_glfw.ns.url_open_callback || !urls) return;
for (id x in urls) {
NSURL *ns_url = x;
const char *url = NULL;
@try {
url = [[ns_url absoluteString] UTF8String];
} @catch(NSException *exc) {
NSLog(@"Converting openURLs url: %@ failed with error: %@", ns_url, exc.reason);
}
if (url) _glfw.ns.url_open_callback(url);
}
}
@ -391,7 +408,7 @@ static GLFWapplicationwillfinishlaunchingfun finish_launching_callback = NULL;
{
(void)notification;
[NSApp stop:nil];
if (_glfw.ns.file_open_callback) _glfw.ns.file_open_callback(":cocoa::application launched::");
if (_glfw.ns.url_open_callback) _glfw.ns.url_open_callback(":cocoa::application launched::");
CGDisplayRegisterReconfigurationCallback(display_reconfigured, NULL);
_glfwCocoaPostEmptyEvent();

View File

@ -68,7 +68,7 @@ typedef void* CVDisplayLinkRef;
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int, unsigned long);
typedef bool (* GLFWapplicationshouldhandlereopenfun)(int);
typedef bool (* GLFWhandlefileopen)(const char*);
typedef bool (* GLFWhandleurlopen)(const char*);
typedef void (* GLFWapplicationwillfinishlaunchingfun)(void);
typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
typedef void (* GLFWcocoarenderframefun)(GLFWwindow*);
@ -199,8 +199,8 @@ typedef struct _GLFWlibraryNS
_GLFWDisplayLinkNS entries[256];
size_t count;
} displayLinks;
// the callback to handle file open events
GLFWhandlefileopen file_open_callback;
// the callback to handle url open events
GLFWhandleurlopen url_open_callback;
} _GLFWlibraryNS;

View File

@ -2642,10 +2642,10 @@ GLFWAPI GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow *hand
return previous;
}
GLFWAPI GLFWhandlefileopen glfwSetCocoaFileOpenCallback(GLFWhandlefileopen callback) {
GLFWAPI GLFWhandleurlopen glfwSetCocoaURLOpenCallback(GLFWhandleurlopen callback) {
_GLFW_REQUIRE_INIT_OR_RETURN(nil);
GLFWhandlefileopen prev = _glfw.ns.file_open_callback;
_glfw.ns.file_open_callback = callback;
GLFWhandleurlopen prev = _glfw.ns.url_open_callback;
_glfw.ns.url_open_callback = callback;
return prev;
}

View File

@ -233,7 +233,7 @@ def generate_wrappers(glfw_header: str) -> None:
void* glfwGetNSGLContext(GLFWwindow *window)
uint32_t glfwGetCocoaMonitor(GLFWmonitor* monitor)
GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow* window, GLFWcocoatextinputfilterfun callback)
GLFWhandlefileopen glfwSetCocoaFileOpenCallback(GLFWhandlefileopen callback)
GLFWhandleurlopen glfwSetCocoaURLOpenCallback(GLFWhandleurlopen callback)
GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *window, GLFWcocoatogglefullscreenfun callback)
GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback)
GLFWapplicationwillfinishlaunchingfun glfwSetApplicationWillFinishLaunching(GLFWapplicationwillfinishlaunchingfun callback)
@ -277,7 +277,7 @@ const char *action_text, int32_t timeout, GLFWDBusnotificationcreatedfun callbac
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int,unsigned long);
typedef bool (* GLFWapplicationshouldhandlereopenfun)(int);
typedef bool (* GLFWhandlefileopen)(const char*);
typedef bool (* GLFWhandleurlopen)(const char*);
typedef void (* GLFWapplicationwillfinishlaunchingfun)(void);
typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
typedef void (* GLFWcocoarenderframefun)(GLFWwindow*);
@ -292,6 +292,8 @@ const char* load_glfw(const char* path);
f.write(header)
code = '''
// generated by glfw.py DO NOT edit
#define GFW_EXTERN
#include "data-types.h"
#include "glfw-wrapper.h"

View File

@ -36,7 +36,7 @@ def patch_conf(raw: str, theme_name: str) -> str:
nraw = raw + addition
# comment out all existing color definitions
color_conf_items = ( # {{{
# generated by gen_config.py do not EDIT
# generated by gen-config.py DO NOT edit
# ALL_COLORS_START
'active_border_color',
'active_tab_background',

View File

@ -2234,13 +2234,13 @@ 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 open_file(self, path: str) -> None:
if path == ":cocoa::application launched::":
def launch_url(self, url: str) -> None:
if url == ":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(path))
actions = list(actions_for_launch(url))
tab = self.active_tab
if tab is not None:
w = tab.active_window
@ -2254,7 +2254,7 @@ class Boss:
if not actions:
with force_window_launch(needs_window_replaced):
self.launch(kitty_exe(), '+runpy', f'print("The file:", {path!r}, "is of unknown type, cannot open it.");'
self.launch(kitty_exe(), '+runpy', f'print("The url:", {url!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:

View File

@ -1001,18 +1001,18 @@ static bool cocoa_pending_actions[NUM_COCOA_PENDING_ACTIONS] = {0};
static bool has_cocoa_pending_actions = false;
typedef struct {
char* wd;
char **open_files;
size_t open_files_count;
size_t open_files_capacity;
char **open_urls;
size_t open_urls_count;
size_t open_urls_capacity;
} CocoaPendingActionsData;
static CocoaPendingActionsData cocoa_pending_actions_data = {0};
void
set_cocoa_pending_action(CocoaPendingAction action, const char *wd) {
if (wd) {
if (action == OPEN_FILE) {
ensure_space_for(&cocoa_pending_actions_data, open_files, char*, cocoa_pending_actions_data.open_files_count + 8, open_files_capacity, 8, true);
cocoa_pending_actions_data.open_files[cocoa_pending_actions_data.open_files_count++] = strdup(wd);
if (action == LAUNCH_URL) {
ensure_space_for(&cocoa_pending_actions_data, open_urls, char*, cocoa_pending_actions_data.open_urls_count + 8, open_urls_capacity, 8, true);
cocoa_pending_actions_data.open_urls[cocoa_pending_actions_data.open_urls_count++] = strdup(wd);
} else {
if (cocoa_pending_actions_data.wd) free(cocoa_pending_actions_data.wd);
cocoa_pending_actions_data.wd = strdup(wd);
@ -1047,15 +1047,15 @@ process_cocoa_pending_actions(void) {
free(cocoa_pending_actions_data.wd);
cocoa_pending_actions_data.wd = NULL;
}
if (cocoa_pending_actions_data.open_files_count) {
for (unsigned cpa = 0; cpa < cocoa_pending_actions_data.open_files_count; cpa++) {
if (cocoa_pending_actions_data.open_files[cpa]) {
call_boss(open_file, "s", cocoa_pending_actions_data.open_files[cpa]);
free(cocoa_pending_actions_data.open_files[cpa]);
cocoa_pending_actions_data.open_files[cpa] = NULL;
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]);
free(cocoa_pending_actions_data.open_urls[cpa]);
cocoa_pending_actions_data.open_urls[cpa] = NULL;
}
}
cocoa_pending_actions_data.open_files_count = 0;
cocoa_pending_actions_data.open_urls_count = 0;
}
memset(cocoa_pending_actions, 0, sizeof(cocoa_pending_actions));
has_cocoa_pending_actions = false;
@ -1115,11 +1115,11 @@ main_loop(ChildMonitor *self, PyObject *a UNUSED) {
run_main_loop(process_global_state, self);
#ifdef __APPLE__
if (cocoa_pending_actions_data.wd) { free(cocoa_pending_actions_data.wd); cocoa_pending_actions_data.wd = NULL; }
if (cocoa_pending_actions_data.open_files) {
for (unsigned cpa = 0; cpa < cocoa_pending_actions_data.open_files_count; cpa++) {
if (cocoa_pending_actions_data.open_files[cpa]) free(cocoa_pending_actions_data.open_files[cpa]);
if (cocoa_pending_actions_data.open_urls) {
for (unsigned cpa = 0; cpa < cocoa_pending_actions_data.open_urls_count; cpa++) {
if (cocoa_pending_actions_data.open_urls[cpa]) free(cocoa_pending_actions_data.open_urls[cpa]);
}
free(cocoa_pending_actions_data.open_files); cocoa_pending_actions_data.open_files = NULL;
free(cocoa_pending_actions_data.open_urls); cocoa_pending_actions_data.open_urls = NULL;
}
#endif
if (PyErr_Occurred()) return NULL;

6
kitty/glfw-wrapper.c generated
View File

@ -1,4 +1,6 @@
// generated by glfw.py DO NOT edit
#define GFW_EXTERN
#include "data-types.h"
#include "glfw-wrapper.h"
@ -405,8 +407,8 @@ load_glfw(const char* path) {
*(void **) (&glfwSetCocoaTextInputFilter_impl) = dlsym(handle, "glfwSetCocoaTextInputFilter");
if (glfwSetCocoaTextInputFilter_impl == NULL) dlerror(); // clear error indicator
*(void **) (&glfwSetCocoaFileOpenCallback_impl) = dlsym(handle, "glfwSetCocoaFileOpenCallback");
if (glfwSetCocoaFileOpenCallback_impl == NULL) dlerror(); // clear error indicator
*(void **) (&glfwSetCocoaURLOpenCallback_impl) = dlsym(handle, "glfwSetCocoaURLOpenCallback");
if (glfwSetCocoaURLOpenCallback_impl == NULL) dlerror(); // clear error indicator
*(void **) (&glfwSetCocoaToggleFullscreenIntercept_impl) = dlsym(handle, "glfwSetCocoaToggleFullscreenIntercept");
if (glfwSetCocoaToggleFullscreenIntercept_impl == NULL) dlerror(); // clear error indicator

8
kitty/glfw-wrapper.h generated
View File

@ -1597,7 +1597,7 @@ typedef struct GLFWgamepadstate
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int,unsigned long);
typedef bool (* GLFWapplicationshouldhandlereopenfun)(int);
typedef bool (* GLFWhandlefileopen)(const char*);
typedef bool (* GLFWhandleurlopen)(const char*);
typedef void (* GLFWapplicationwillfinishlaunchingfun)(void);
typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
typedef void (* GLFWcocoarenderframefun)(GLFWwindow*);
@ -2124,9 +2124,9 @@ typedef GLFWcocoatextinputfilterfun (*glfwSetCocoaTextInputFilter_func)(GLFWwind
GFW_EXTERN glfwSetCocoaTextInputFilter_func glfwSetCocoaTextInputFilter_impl;
#define glfwSetCocoaTextInputFilter glfwSetCocoaTextInputFilter_impl
typedef GLFWhandlefileopen (*glfwSetCocoaFileOpenCallback_func)(GLFWhandlefileopen);
GFW_EXTERN glfwSetCocoaFileOpenCallback_func glfwSetCocoaFileOpenCallback_impl;
#define glfwSetCocoaFileOpenCallback glfwSetCocoaFileOpenCallback_impl
typedef GLFWhandleurlopen (*glfwSetCocoaURLOpenCallback_func)(GLFWhandleurlopen);
GFW_EXTERN glfwSetCocoaURLOpenCallback_func glfwSetCocoaURLOpenCallback_impl;
#define glfwSetCocoaURLOpenCallback glfwSetCocoaURLOpenCallback_impl
typedef GLFWcocoatogglefullscreenfun (*glfwSetCocoaToggleFullscreenIntercept_func)(GLFWwindow*, GLFWcocoatogglefullscreenfun);
GFW_EXTERN glfwSetCocoaToggleFullscreenIntercept_func glfwSetCocoaToggleFullscreenIntercept_impl;

View File

@ -455,8 +455,8 @@ static void get_window_dpi(GLFWwindow *w, double *x, double *y);
#ifdef __APPLE__
static bool
apple_file_open_callback(const char* filepath) {
set_cocoa_pending_action(OPEN_FILE, filepath);
apple_url_open_callback(const char* url) {
set_cocoa_pending_action(LAUNCH_URL, url);
return true;
}
@ -1033,7 +1033,7 @@ glfw_init(PyObject UNUSED *self, PyObject *args) {
PyObject *ans = glfwInit(monotonic_start_time) ? Py_True: Py_False;
if (ans == Py_True) {
#ifdef __APPLE__
glfwSetCocoaFileOpenCallback(apple_file_open_callback);
glfwSetCocoaURLOpenCallback(apple_url_open_callback);
#else
glfwSetDrawTextFunction(draw_text_callback);
#endif

View File

@ -9,7 +9,7 @@ from contextlib import suppress
from typing import (
Any, Dict, Iterable, Iterator, List, NamedTuple, Optional, Tuple, cast
)
from urllib.parse import ParseResult, quote, unquote, urlparse
from urllib.parse import ParseResult, unquote, urlparse
from .conf.utils import KeyAction, to_cmdline_implementation
from .constants import config_dir
@ -243,6 +243,10 @@ action launch --type=os-window $EDITOR $FILE_PATH
protocol file
mime image/*
action launch --type=os-window kitty +kitten icat --hold $FILE_PATH
# Open ssh URLs with ssh command
protocol ssh
action launch --type=os-window ssh $URL
'''.splitlines()))
@ -254,8 +258,10 @@ def actions_for_url(url: str, actions_spec: Optional[str] = None) -> Iterator[Ke
yield from actions_for_url_from_list(url, actions)
def actions_for_launch(path: str) -> Iterator[KeyAction]:
url = f'file://{quote(path)}'
def actions_for_launch(url: str) -> Iterator[KeyAction]:
# Custom launch actions using kitty URL scheme needs to be prefixed with `kitty:///launch/`
if url.startswith('kitty://') and not url.startswith('kitty:///launch/'):
return
found = False
for action in actions_for_url_from_list(url, load_launch_actions()):
found = True

View File

@ -377,7 +377,7 @@ The program with which to open URLs that are clicked on. The special value
'''
)
opt('url_prefixes', 'http https file ftp gemini irc gopher mailto news git',
opt('url_prefixes', 'file ftp ftps gemini git gopher http https irc ircs kitty mailto news sftp ssh',
option_type='url_prefixes', ctype='!url_prefixes',
long_text='''
The set of URL prefixes to look for when detecting a URL under the mouse cursor.

View File

@ -298,7 +298,7 @@ typedef enum {
NEXT_TAB,
PREVIOUS_TAB,
DETACH_TAB,
OPEN_FILE,
LAUNCH_URL,
NEW_WINDOW,
CLOSE_WINDOW,
RESET_TERMINAL,

View File

@ -1026,6 +1026,74 @@ def macos_info_plist() -> bytes:
},
]
url_schemes = [
{
'CFBundleURLName': 'File URL',
'CFBundleURLSchemes': ['file'],
},
{
'CFBundleURLName': 'FTP URL',
'CFBundleURLSchemes': ['ftp', 'ftps'],
},
{
'CFBundleURLName': 'Gemini URL',
'CFBundleURLSchemes': ['gemini'],
},
{
'CFBundleURLName': 'Git URL',
'CFBundleURLSchemes': ['git'],
},
{
'CFBundleURLName': 'Gopher URL',
'CFBundleURLSchemes': ['gopher'],
},
{
'CFBundleURLName': 'HTTP URL',
'CFBundleURLSchemes': ['http', 'https'],
},
{
'CFBundleURLName': 'IRC URL',
'CFBundleURLSchemes': ['irc', 'irc6', 'ircs'],
},
{
'CFBundleURLName': 'kitty URL',
'CFBundleURLSchemes': ['kitty'],
'LSHandlerRank': 'Owner',
'LSIsAppleDefaultForScheme': True,
},
{
'CFBundleURLName': 'Mail Address URL',
'CFBundleURLSchemes': ['mailto'],
},
{
'CFBundleURLName': 'News URL',
'CFBundleURLSchemes': ['news', 'nntp'],
},
{
'CFBundleURLName': 'SSH and SFTP URL',
'CFBundleURLSchemes': ['ssh', 'sftp'],
},
{
'CFBundleURLName': 'Telnet URL',
'CFBundleURLSchemes': ['telnet'],
},
]
services = [
{
'NSMenuItem': {'default': f'New {appname} Tab Here'},
'NSMessage': 'openTab',
'NSRequiredContext': {'NSTextContent': 'FilePath'},
'NSSendTypes': ['NSFilenamesPboardType', 'public.plain-text'],
},
{
'NSMenuItem': {'default': f'New {appname} Window Here'},
'NSMessage': 'openOSWindow',
'NSRequiredContext': {'NSTextContent': 'FilePath'},
'NSSendTypes': ['NSFilenamesPboardType', 'public.plain-text'],
},
]
pl = dict(
# Naming
CFBundleName=appname,
@ -1043,7 +1111,6 @@ def macos_info_plist() -> bytes:
# Categorization
CFBundlePackageType='APPL',
CFBundleSignature='????',
CFBundleDocumentTypes=docs,
LSApplicationCategoryType='public.app-category.utilities',
# App Execution
CFBundleExecutable=appname,
@ -1061,21 +1128,11 @@ def macos_info_plist() -> bytes:
NSSupportsAutomaticGraphicsSwitching=True,
# Needed for dark mode in Mojave when linking against older SDKs
NSRequiresAquaSystemAppearance='NO',
# Document and URL Types
CFBundleDocumentTypes=docs,
CFBundleURLTypes=url_schemes,
# Services
NSServices=[
{
'NSMenuItem': {'default': f'New {appname} Tab Here'},
'NSMessage': 'openTab',
'NSRequiredContext': {'NSTextContent': 'FilePath'},
'NSSendTypes': ['NSFilenamesPboardType', 'public.plain-text'],
},
{
'NSMenuItem': {'default': f'New {appname} Window Here'},
'NSMessage': 'openOSWindow',
'NSRequiredContext': {'NSTextContent': 'FilePath'},
'NSSendTypes': ['NSFilenamesPboardType', 'public.plain-text'],
},
],
NSServices=services,
# Calendar and Reminders
NSCalendarsUsageDescription=access('your calendar data.'),
NSRemindersUsageDescription=access('your reminders.'),