macOS: Allow to set custom app icon automatically
This commit is contained in:
parent
98eacb2067
commit
a1029418f8
@ -40,6 +40,8 @@ Detailed list of changes
|
|||||||
|
|
||||||
- X11: Fix a regression in the previous release that caused pasting from GTK based applications to have extra newlines (:iss:`5528`)
|
- X11: Fix a regression in the previous release that caused pasting from GTK based applications to have extra newlines (:iss:`5528`)
|
||||||
|
|
||||||
|
- macOS: Allow to set custom app icon automatically (:pull:`5464`)
|
||||||
|
|
||||||
0.26.3 [2022-09-22]
|
0.26.3 [2022-09-22]
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|||||||
19
docs/faq.rst
19
docs/faq.rst
@ -287,7 +287,24 @@ homepage:
|
|||||||
:target: https://github.com/samholmes/whiskers
|
:target: https://github.com/samholmes/whiskers
|
||||||
:width: 256
|
:width: 256
|
||||||
|
|
||||||
On macOS you can change the icon by following the steps:
|
On macOS you can put :file:`kitty.app.icns` or :file:`kitty.app.png` in the
|
||||||
|
:ref:`kitty configuration directory <confloc>`, and this icon will be applied
|
||||||
|
automatically at startup if the app bundle has no custom icon. This is
|
||||||
|
convenient because app updates under macOS will replace the entire app bundle
|
||||||
|
and the custom icon will be removed as well. To automatically update a new icon
|
||||||
|
at startup, you need to remove the custom icon on :file:`kitty.app` first.
|
||||||
|
|
||||||
|
You can set custom icon via CLI, which can be used in shell scripts:
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
# Set kitty.icns as the icon for currently running kitty
|
||||||
|
kitty +runpy 'from kitty.fast_data_types import cocoa_set_app_icon; import sys; cocoa_set_app_icon(*sys.argv[1:]); print("OK")' kitty.icns
|
||||||
|
|
||||||
|
# Set the icon for app bundle specified by the path
|
||||||
|
kitty +runpy 'from kitty.fast_data_types import cocoa_set_app_icon; import sys; cocoa_set_app_icon(*sys.argv[1:]); print("OK")' /path/to/icon.png /Applications/kitty.app
|
||||||
|
|
||||||
|
You can also change the icon manually by following the steps:
|
||||||
|
|
||||||
#. Find :file:`kitty.app` in the Applications folder, select it and press :kbd:`⌘+I`
|
#. Find :file:`kitty.app` in the Applications folder, select it and press :kbd:`⌘+I`
|
||||||
#. Drag :file:`kitty.icns` onto the application icon in the kitty info pane
|
#. Drag :file:`kitty.icns` onto the application icon in the kitty info pane
|
||||||
|
|||||||
@ -862,6 +862,98 @@ cocoa_set_url_handler(PyObject UNUSED *self, PyObject *args) {
|
|||||||
} // autoreleasepool
|
} // autoreleasepool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
cocoa_app_has_custom_icon(PyObject UNUSED *self, PyObject *args) {
|
||||||
|
@autoreleasepool {
|
||||||
|
|
||||||
|
const char *app_path = NULL;
|
||||||
|
if (!PyArg_ParseTuple(args, "|z", &app_path)) return NULL;
|
||||||
|
NSString *bundle_path;
|
||||||
|
if (app_path && app_path[0] != '\0') bundle_path = [NSString stringWithUTF8String:app_path];
|
||||||
|
else bundle_path = [[NSBundle mainBundle] bundlePath];
|
||||||
|
if (!bundle_path || bundle_path.length == 0) bundle_path = @"/Applications/kitty.app";
|
||||||
|
|
||||||
|
// These APIs have been marked as deprecated.
|
||||||
|
// However support for NSURLCustomIconKey has never been implemented by Apple (so far, macOS 12.5.x and below).
|
||||||
|
// so the following NSImage icon_image will be nil even if a custom icon is set:
|
||||||
|
// [[NSURL fileURLWithPath:bundle_path] getResourceValue:&icon_image forKey:NSURLCustomIconKey error:nil]
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||||
|
FSRef ref;
|
||||||
|
FSCatalogInfo catalog_info;
|
||||||
|
OSStatus err = FSPathMakeRef((const UInt8 *)[bundle_path fileSystemRepresentation], &ref, NULL);
|
||||||
|
if (err == noErr) {
|
||||||
|
err = FSGetCatalogInfo(&ref, kFSCatInfoFinderInfo, &catalog_info, NULL, NULL, NULL);
|
||||||
|
if (err == noErr) {
|
||||||
|
FileInfo *file_info = (FileInfo *)(&catalog_info.finderInfo);
|
||||||
|
if ((file_info->finderFlags & kHasCustomIcon) != 0) Py_RETURN_TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
Py_RETURN_FALSE;
|
||||||
|
|
||||||
|
} // autoreleasepool
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
cocoa_set_app_icon(PyObject UNUSED *self, PyObject *args) {
|
||||||
|
@autoreleasepool {
|
||||||
|
|
||||||
|
const char *icon_path = NULL, *app_path = NULL;
|
||||||
|
if (!PyArg_ParseTuple(args, "s|z", &icon_path, &app_path)) return NULL;
|
||||||
|
if (!icon_path || icon_path[0] == '\0') {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "Empty icon file path");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *custom_icon_path = [NSString stringWithUTF8String:icon_path];
|
||||||
|
NSString *bundle_path = @"";
|
||||||
|
if (!app_path) {
|
||||||
|
bundle_path = [[NSBundle mainBundle] bundlePath];
|
||||||
|
if (!bundle_path || bundle_path.length == 0) bundle_path = @"/Applications/kitty.app";
|
||||||
|
} else if (app_path[0] != '\0') {
|
||||||
|
bundle_path = [NSString stringWithUTF8String:app_path];
|
||||||
|
}
|
||||||
|
if (![[NSFileManager defaultManager] fileExistsAtPath:custom_icon_path]) {
|
||||||
|
PyErr_Format(PyExc_FileNotFoundError, "Icon file not found: %s", [custom_icon_path UTF8String]);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (![[NSFileManager defaultManager] fileExistsAtPath:bundle_path]) {
|
||||||
|
PyErr_Format(PyExc_FileNotFoundError, "Application bundle not found: %s", [bundle_path UTF8String]);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSImage *icon_image = [[NSImage alloc] initWithContentsOfFile:custom_icon_path];
|
||||||
|
BOOL result = [[NSWorkspace sharedWorkspace] setIcon:icon_image forFile:bundle_path options:NSExcludeQuickDrawElementsIconCreationOption];
|
||||||
|
[icon_image release];
|
||||||
|
if (result) Py_RETURN_NONE;
|
||||||
|
PyErr_Format(PyExc_OSError, "Failed to set custom icon %s for %s", [custom_icon_path UTF8String], [bundle_path UTF8String]);
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
} // autoreleasepool
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
cocoa_set_dock_icon(PyObject UNUSED *self, PyObject *args) {
|
||||||
|
@autoreleasepool {
|
||||||
|
|
||||||
|
const char *icon_path = NULL;
|
||||||
|
if (!PyArg_ParseTuple(args, "s", &icon_path)) return NULL;
|
||||||
|
if (!icon_path || icon_path[0] == '\0') {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "Empty icon file path");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
NSString *custom_icon_path = [NSString stringWithUTF8String:icon_path];
|
||||||
|
if ([[NSFileManager defaultManager] fileExistsAtPath:custom_icon_path]) {
|
||||||
|
NSImage *icon_image = [[[NSImage alloc] initWithContentsOfFile:custom_icon_path] autorelease];
|
||||||
|
[NSApplication sharedApplication].applicationIconImage = icon_image;
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
} // autoreleasepool
|
||||||
|
}
|
||||||
|
|
||||||
static NSSound *beep_sound = nil;
|
static NSSound *beep_sound = nil;
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -923,6 +1015,9 @@ static PyMethodDef module_methods[] = {
|
|||||||
{"cocoa_send_notification", (PyCFunction)cocoa_send_notification, METH_VARARGS, ""},
|
{"cocoa_send_notification", (PyCFunction)cocoa_send_notification, METH_VARARGS, ""},
|
||||||
{"cocoa_set_notification_activated_callback", (PyCFunction)set_notification_activated_callback, METH_O, ""},
|
{"cocoa_set_notification_activated_callback", (PyCFunction)set_notification_activated_callback, METH_O, ""},
|
||||||
{"cocoa_set_url_handler", (PyCFunction)cocoa_set_url_handler, METH_VARARGS, ""},
|
{"cocoa_set_url_handler", (PyCFunction)cocoa_set_url_handler, METH_VARARGS, ""},
|
||||||
|
{"cocoa_app_has_custom_icon", (PyCFunction)cocoa_app_has_custom_icon, METH_VARARGS, ""},
|
||||||
|
{"cocoa_set_app_icon", (PyCFunction)cocoa_set_app_icon, METH_VARARGS, ""},
|
||||||
|
{"cocoa_set_dock_icon", (PyCFunction)cocoa_set_dock_icon, METH_VARARGS, ""},
|
||||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -730,7 +730,19 @@ def cocoa_get_lang() -> Optional[str]:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def cocoa_set_url_handler(url_scheme: str, bundle_id: Optional[str]) -> None:
|
def cocoa_set_url_handler(url_scheme: str, bundle_id: Optional[str] = None) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def cocoa_app_has_custom_icon(app_path: Optional[str] = None) -> bool:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def cocoa_set_app_icon(icon_path: str, app_path: Optional[str] = None) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def cocoa_set_dock_icon(icon_path: str) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -135,6 +135,20 @@ def get_macos_shortcut_for(
|
|||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
|
def set_macos_app_custom_icon() -> None:
|
||||||
|
for name in ('kitty.app.icns', 'kitty.app.png'):
|
||||||
|
icon_path = os.path.join(config_dir, name)
|
||||||
|
if os.path.exists(icon_path):
|
||||||
|
from .fast_data_types import cocoa_app_has_custom_icon, cocoa_set_app_icon, cocoa_set_dock_icon
|
||||||
|
if not cocoa_app_has_custom_icon():
|
||||||
|
cocoa_set_app_icon(icon_path)
|
||||||
|
# kitty dock icon doesn't refresh automatically, so set it explicitly
|
||||||
|
# This has the drawback that the dock icon reverts to the original icon after exiting the application,
|
||||||
|
# even if the custom icon has been successfully updated, until the next launch.
|
||||||
|
cocoa_set_dock_icon(icon_path)
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
def set_x11_window_icon() -> None:
|
def set_x11_window_icon() -> None:
|
||||||
# max icon size on X11 64bits is 128x128
|
# max icon size on X11 64bits is 128x128
|
||||||
path, ext = os.path.splitext(logo_png_file)
|
path, ext = os.path.splitext(logo_png_file)
|
||||||
@ -167,8 +181,11 @@ def _run_app(opts: Options, args: CLIOptions, prewarm: PrewarmProcess, bad_lines
|
|||||||
val = get_macos_shortcut_for(func_map, f'open_url {website_url()}', lookup_name='open_kitty_website')
|
val = get_macos_shortcut_for(func_map, f'open_url {website_url()}', lookup_name='open_kitty_website')
|
||||||
if val is not None:
|
if val is not None:
|
||||||
global_shortcuts['open_kitty_website'] = val
|
global_shortcuts['open_kitty_website'] = val
|
||||||
if is_macos and opts.macos_custom_beam_cursor:
|
|
||||||
set_custom_ibeam_cursor()
|
if opts.macos_custom_beam_cursor:
|
||||||
|
set_custom_ibeam_cursor()
|
||||||
|
set_macos_app_custom_icon()
|
||||||
|
|
||||||
if not is_wayland() and not is_macos: # no window icons on wayland
|
if not is_wayland() and not is_macos: # no window icons on wayland
|
||||||
set_x11_window_icon()
|
set_x11_window_icon()
|
||||||
with cached_values_for(run_app.cached_values_name) as cached_values:
|
with cached_values_for(run_app.cached_values_name) as cached_values:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user