parent
0f020d5b37
commit
f70c9842f5
@ -55,6 +55,8 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
|
||||
|
||||
- macOS: Add menu items to close the OS window and the current tab (:pull:`3240`, :iss:`3246`)
|
||||
|
||||
- macOS: Allow opening script and command files with kitty (:iss:`3366`)
|
||||
|
||||
- Also detect ``gemini://`` URLs when hovering with the mouse (:iss:`3370`)
|
||||
|
||||
- When using a non-US keyboard layout and pressing :kbd:`ctrl+key` when
|
||||
|
||||
@ -346,10 +346,25 @@ 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;
|
||||
@try {
|
||||
path = [[NSFileManager defaultManager] fileSystemRepresentationWithPath: filename];
|
||||
} @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);
|
||||
}
|
||||
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)notification
|
||||
{
|
||||
(void)notification;
|
||||
[NSApp stop:nil];
|
||||
if (_glfw.ns.file_open_callback) _glfw.ns.file_open_callback(":cocoa::application launched::");
|
||||
|
||||
CGDisplayRegisterReconfigurationCallback(display_reconfigured, NULL);
|
||||
_glfwCocoaPostEmptyEvent();
|
||||
|
||||
3
glfw/cocoa_platform.h
vendored
3
glfw/cocoa_platform.h
vendored
@ -67,6 +67,7 @@ typedef void* CVDisplayLinkRef;
|
||||
|
||||
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int, unsigned long);
|
||||
typedef bool (* GLFWapplicationshouldhandlereopenfun)(int);
|
||||
typedef bool (* GLFWhandlefileopen)(const char*);
|
||||
typedef void (* GLFWapplicationwillfinishlaunchingfun)(void);
|
||||
typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
|
||||
typedef void (* GLFWcocoarenderframefun)(GLFWwindow*);
|
||||
@ -197,6 +198,8 @@ typedef struct _GLFWlibraryNS
|
||||
_GLFWDisplayLinkNS entries[256];
|
||||
size_t count;
|
||||
} displayLinks;
|
||||
// the callback to handle file open events
|
||||
GLFWhandlefileopen file_open_callback;
|
||||
|
||||
} _GLFWlibraryNS;
|
||||
|
||||
|
||||
@ -2482,6 +2482,13 @@ GLFWAPI GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow *hand
|
||||
return previous;
|
||||
}
|
||||
|
||||
GLFWAPI GLFWhandlefileopen glfwSetCocoaFileOpenCallback(GLFWhandlefileopen callback) {
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(nil);
|
||||
GLFWhandlefileopen prev = _glfw.ns.file_open_callback;
|
||||
_glfw.ns.file_open_callback = callback;
|
||||
return prev;
|
||||
}
|
||||
|
||||
GLFWAPI GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *handle, GLFWcocoatogglefullscreenfun callback) {
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(nil);
|
||||
|
||||
@ -207,6 +207,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)
|
||||
GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *window, GLFWcocoatogglefullscreenfun callback)
|
||||
GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback)
|
||||
GLFWapplicationwillfinishlaunchingfun glfwSetApplicationWillFinishLaunching(GLFWapplicationwillfinishlaunchingfun callback)
|
||||
@ -248,6 +249,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 void (* GLFWapplicationwillfinishlaunchingfun)(void);
|
||||
typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
|
||||
typedef void (* GLFWcocoarenderframefun)(GLFWwindow*);
|
||||
|
||||
@ -144,6 +144,7 @@ class Boss:
|
||||
global_shortcuts: Dict[str, SingleKey]
|
||||
):
|
||||
set_layout_options(opts)
|
||||
self.cocoa_application_launched = False
|
||||
self.clipboard_buffers: Dict[str, str] = {}
|
||||
self.update_check_process: Optional[PopenType] = None
|
||||
self.window_id_map: WeakValueDictionary[int, Window] = WeakValueDictionary()
|
||||
@ -1641,3 +1642,21 @@ class Boss:
|
||||
if w:
|
||||
output = '\n'.join(f'{k}={v}' for k, v in os.environ.items()).encode('utf-8')
|
||||
self.display_scrollback(w, output, ['less'])
|
||||
|
||||
def open_file(self, path: str) -> None:
|
||||
if path == ":cocoa::application launched::":
|
||||
self.cocoa_application_launched = True
|
||||
return
|
||||
|
||||
def new_os_window() -> None:
|
||||
self.new_os_window(path)
|
||||
|
||||
if self.cocoa_application_launched or not self.os_window_map:
|
||||
return new_os_window()
|
||||
tab = self.active_tab
|
||||
if tab is None:
|
||||
return new_os_window()
|
||||
w = tab.active_window
|
||||
self.new_window(path)
|
||||
if w is not None:
|
||||
tab.remove_window(w)
|
||||
|
||||
@ -935,14 +935,25 @@ process_pending_closes(ChildMonitor *self) {
|
||||
// If we create new OS windows during wait_events(), using global menu actions
|
||||
// via the mouse causes a crash because of the way autorelease pools work in
|
||||
// glfw/cocoa. So we use a flag instead.
|
||||
static unsigned int cocoa_pending_actions = 0;
|
||||
static char *cocoa_pending_actions_wd = NULL;
|
||||
static CocoaPendingAction cocoa_pending_actions = NO_COCOA_PENDING_ACTION;
|
||||
typedef struct {
|
||||
char* wd;
|
||||
char **open_files;
|
||||
size_t open_files_count;
|
||||
size_t open_files_capacity;
|
||||
} CocoaPendingActionsData;
|
||||
static CocoaPendingActionsData cocoa_pending_actions_data = {0};
|
||||
|
||||
void
|
||||
set_cocoa_pending_action(CocoaPendingAction action, const char *wd) {
|
||||
if (wd) {
|
||||
if (cocoa_pending_actions_wd) free(cocoa_pending_actions_wd);
|
||||
cocoa_pending_actions_wd = strdup(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);
|
||||
} else {
|
||||
if (cocoa_pending_actions_data.wd) free(cocoa_pending_actions_data.wd);
|
||||
cocoa_pending_actions_data.wd = strdup(wd);
|
||||
}
|
||||
}
|
||||
cocoa_pending_actions |= action;
|
||||
// The main loop may be blocking on the event queue, if e.g. unfocused.
|
||||
@ -986,11 +997,21 @@ process_global_state(void *data) {
|
||||
if (cocoa_pending_actions & NEXT_TAB) { call_boss(next_tab, NULL); }
|
||||
if (cocoa_pending_actions & PREVIOUS_TAB) { call_boss(previous_tab, NULL); }
|
||||
if (cocoa_pending_actions & DETACH_TAB) { call_boss(detach_tab, NULL); }
|
||||
if (cocoa_pending_actions_wd) {
|
||||
if (cocoa_pending_actions & NEW_OS_WINDOW_WITH_WD) { call_boss(new_os_window_with_wd, "s", cocoa_pending_actions_wd); }
|
||||
if (cocoa_pending_actions & NEW_TAB_WITH_WD) { call_boss(new_tab_with_wd, "s", cocoa_pending_actions_wd); }
|
||||
free(cocoa_pending_actions_wd);
|
||||
cocoa_pending_actions_wd = NULL;
|
||||
if (cocoa_pending_actions_data.wd) {
|
||||
if (cocoa_pending_actions & NEW_OS_WINDOW_WITH_WD) { call_boss(new_os_window_with_wd, "s", cocoa_pending_actions_data.wd); }
|
||||
if (cocoa_pending_actions & NEW_TAB_WITH_WD) { call_boss(new_tab_with_wd, "s", cocoa_pending_actions_data.wd); }
|
||||
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;
|
||||
}
|
||||
}
|
||||
cocoa_pending_actions_data.open_files_count = 0;
|
||||
}
|
||||
cocoa_pending_actions = 0;
|
||||
}
|
||||
@ -1015,7 +1036,13 @@ main_loop(ChildMonitor *self, PyObject *a UNUSED) {
|
||||
state_check_timer = add_main_loop_timer(1000, true, do_state_check, self, NULL);
|
||||
run_main_loop(process_global_state, self);
|
||||
#ifdef __APPLE__
|
||||
if (cocoa_pending_actions_wd) { free(cocoa_pending_actions_wd); cocoa_pending_actions_wd = NULL; }
|
||||
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]);
|
||||
}
|
||||
free(cocoa_pending_actions_data.open_files); cocoa_pending_actions_data.open_files = NULL;
|
||||
}
|
||||
#endif
|
||||
if (PyErr_Occurred()) return NULL;
|
||||
Py_RETURN_NONE;
|
||||
|
||||
2
kitty/glfw-wrapper.c
generated
2
kitty/glfw-wrapper.c
generated
@ -388,6 +388,8 @@ load_glfw(const char* path) {
|
||||
|
||||
*(void **) (&glfwSetCocoaTextInputFilter_impl) = dlsym(handle, "glfwSetCocoaTextInputFilter");
|
||||
|
||||
*(void **) (&glfwSetCocoaFileOpenCallback_impl) = dlsym(handle, "glfwSetCocoaFileOpenCallback");
|
||||
|
||||
*(void **) (&glfwSetCocoaToggleFullscreenIntercept_impl) = dlsym(handle, "glfwSetCocoaToggleFullscreenIntercept");
|
||||
|
||||
*(void **) (&glfwSetApplicationShouldHandleReopen_impl) = dlsym(handle, "glfwSetApplicationShouldHandleReopen");
|
||||
|
||||
5
kitty/glfw-wrapper.h
generated
5
kitty/glfw-wrapper.h
generated
@ -1553,6 +1553,7 @@ typedef struct GLFWgamepadstate
|
||||
|
||||
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int,unsigned long);
|
||||
typedef bool (* GLFWapplicationshouldhandlereopenfun)(int);
|
||||
typedef bool (* GLFWhandlefileopen)(const char*);
|
||||
typedef void (* GLFWapplicationwillfinishlaunchingfun)(void);
|
||||
typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
|
||||
typedef void (* GLFWcocoarenderframefun)(GLFWwindow*);
|
||||
@ -2063,6 +2064,10 @@ 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 GLFWcocoatogglefullscreenfun (*glfwSetCocoaToggleFullscreenIntercept_func)(GLFWwindow*, GLFWcocoatogglefullscreenfun);
|
||||
GFW_EXTERN glfwSetCocoaToggleFullscreenIntercept_func glfwSetCocoaToggleFullscreenIntercept_impl;
|
||||
#define glfwSetCocoaToggleFullscreenIntercept glfwSetCocoaToggleFullscreenIntercept_impl
|
||||
|
||||
11
kitty/glfw.c
11
kitty/glfw.c
@ -379,6 +379,14 @@ application_close_requested_callback(int flags) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
static bool
|
||||
apple_file_open_callback(const char* filepath) {
|
||||
set_cocoa_pending_action(OPEN_FILE, filepath);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
// }}}
|
||||
|
||||
void
|
||||
@ -844,6 +852,9 @@ glfw_init(PyObject UNUSED *self, PyObject *args) {
|
||||
#endif
|
||||
PyObject *ans = glfwInit(monotonic_start_time) ? Py_True: Py_False;
|
||||
if (ans == Py_True) {
|
||||
#ifdef __APPLE__
|
||||
glfwSetCocoaFileOpenCallback(apple_file_open_callback);
|
||||
#endif
|
||||
OSWindow w = {0};
|
||||
set_os_window_dpi(&w);
|
||||
global_state.default_dpi.x = w.logical_dpi_x;
|
||||
|
||||
@ -261,6 +261,7 @@ void send_prerendered_sprites_for_window(OSWindow *w);
|
||||
#ifdef __APPLE__
|
||||
void get_cocoa_key_equivalent(uint32_t, int, char *key, size_t key_sz, int*);
|
||||
typedef enum {
|
||||
NO_COCOA_PENDING_ACTION = 0,
|
||||
PREFERENCES_WINDOW = 1,
|
||||
NEW_OS_WINDOW = 2,
|
||||
NEW_OS_WINDOW_WITH_WD = 4,
|
||||
@ -271,6 +272,7 @@ typedef enum {
|
||||
NEXT_TAB = 128,
|
||||
PREVIOUS_TAB = 256,
|
||||
DETACH_TAB = 512,
|
||||
OPEN_FILE = 1024
|
||||
} CocoaPendingAction;
|
||||
void set_cocoa_pending_action(CocoaPendingAction action, const char*);
|
||||
#endif
|
||||
|
||||
19
setup.py
19
setup.py
@ -914,6 +914,24 @@ def macos_info_plist() -> bytes:
|
||||
def access(what: str, verb: str = 'would like to access') -> str:
|
||||
return f'A program running inside kitty {verb} {what}'
|
||||
|
||||
docs = [
|
||||
{
|
||||
'CFBundleTypeName': 'Terminal scripts',
|
||||
'CFBundleTypeExtensions': ['command', 'sh', 'zsh', 'bash', 'fish', 'tool'],
|
||||
'CFBundleTypeIconFile': appname + '.icns',
|
||||
'CFBundleTypeRole': 'Editor',
|
||||
},
|
||||
{
|
||||
'CFBundleTypeName': 'Folders',
|
||||
'CFBundleTypeOSTypes': ['fold'],
|
||||
'CFBundleTypeRole': 'Editor',
|
||||
},
|
||||
{
|
||||
'LSItemContentTypes': ['public.unix-executable'],
|
||||
'CFBundleTypeRole': 'Shell',
|
||||
},
|
||||
]
|
||||
|
||||
pl = dict(
|
||||
# see https://github.com/kovidgoyal/kitty/issues/1233
|
||||
CFBundleDevelopmentRegion='English',
|
||||
@ -927,6 +945,7 @@ def macos_info_plist() -> bytes:
|
||||
CFBundlePackageType='APPL',
|
||||
CFBundleSignature='????',
|
||||
CFBundleExecutable=appname,
|
||||
CFBundleDocumentTypes=docs,
|
||||
LSMinimumSystemVersion='10.12.0',
|
||||
LSRequiresNativeExecution=True,
|
||||
NSAppleScriptEnabled=False,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user