mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-09 23:54:20 +02:00
Implement special symbol names on macOS
This commit is contained in:
@@ -460,7 +460,7 @@ schedule_notification(const char *appname, const char *identifier, const char *t
|
||||
UNUserNotificationCenter *center = get_notification_center_safely();
|
||||
if (!center) return;
|
||||
// Configure the notification's payload.
|
||||
RAII_CoreFoundation(UNMutableNotificationContent, *content, [[UNMutableNotificationContent alloc] init]);
|
||||
UNMutableNotificationContent *content = [[[UNMutableNotificationContent alloc] init] autorelease];
|
||||
if (title) content.title = @(title);
|
||||
if (body) content.body = @(body);
|
||||
if (appname) content.threadIdentifier = @(appname);
|
||||
@@ -1072,13 +1072,7 @@ cocoa_set_uncaught_exception_handler(void) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
convert_image_to_png(NSImage *icon, unsigned image_size, const char *output_path) {
|
||||
NSRect r = NSMakeRect(0, 0, image_size, image_size);
|
||||
RAII_CoreFoundation(CGColorSpaceRef, colorSpace, CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB));
|
||||
RAII_CoreFoundation(CGContextRef, cgContext, CGBitmapContextCreate(NULL, image_size, image_size, 8, 4*image_size, colorSpace, kCGBitmapByteOrderDefault|kCGImageAlphaPremultipliedLast));
|
||||
NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithCGContext:cgContext flipped:NO]; // autoreleased
|
||||
RAII_CoreFoundation(CGImageRef, cg, [icon CGImageForProposedRect:&r context:context hints:nil]);
|
||||
NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithCGImage:cg]; // autoreleased
|
||||
convert_imagerep_to_png(NSBitmapImageRep *rep, const char *output_path) {
|
||||
NSData *png = [rep representationUsingType:NSBitmapImageFileTypePNG properties:@{NSImageCompressionFactor: @1.0}]; // autoreleased
|
||||
|
||||
if (output_path) {
|
||||
@@ -1091,38 +1085,69 @@ convert_image_to_png(NSImage *icon, unsigned image_size, const char *output_path
|
||||
return PyBytes_FromStringAndSize(png.bytes, png.length);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
convert_image_to_png(NSImage *icon, unsigned image_size, const char *output_path) {
|
||||
NSRect r = NSMakeRect(0, 0, image_size, image_size);
|
||||
RAII_CoreFoundation(CGColorSpaceRef, colorSpace, CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB));
|
||||
RAII_CoreFoundation(CGContextRef, cgContext, CGBitmapContextCreate(NULL, image_size, image_size, 8, 4*image_size, colorSpace, kCGBitmapByteOrderDefault|kCGImageAlphaPremultipliedLast));
|
||||
NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithCGContext:cgContext flipped:NO]; // autoreleased
|
||||
CGImageRef cg = [icon CGImageForProposedRect:&r context:context hints:nil];
|
||||
NSBitmapImageRep *rep = [[[NSBitmapImageRep alloc] initWithCGImage:cg] autorelease];
|
||||
return convert_imagerep_to_png(rep, output_path);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
render_emoji(NSString *text, unsigned image_size, const char *output_path) {
|
||||
NSFont *font = [NSFont fontWithName:@"AppleColorEmoji" size:12];
|
||||
CTFontRef ctfont = (__bridge CTFontRef)(font);
|
||||
CGFloat line_height = MAX(1, floor(CTFontGetAscent(ctfont) + CTFontGetDescent(ctfont) + MAX(0, CTFontGetLeading(ctfont)) + 0.5));
|
||||
CGFloat pts_per_px = CTFontGetSize(ctfont) / line_height;
|
||||
CGFloat desired_size = image_size * pts_per_px;
|
||||
NSFont *final_font = [NSFont fontWithName:@"AppleColorEmoji" size:desired_size];
|
||||
NSAttributedString *attr_string = [[[NSAttributedString alloc] initWithString:text attributes:@{NSFontAttributeName: final_font}] autorelease];
|
||||
NSBitmapImageRep *bmp = [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nil pixelsWide:image_size pixelsHigh:image_size bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO colorSpaceName:NSDeviceRGBColorSpace bytesPerRow:0 bitsPerPixel:0] autorelease];
|
||||
[NSGraphicsContext saveGraphicsState];
|
||||
NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithBitmapImageRep:bmp];
|
||||
[NSGraphicsContext setCurrentContext:context];
|
||||
[attr_string drawInRect:NSMakeRect(0, 0, image_size, image_size)];
|
||||
[NSGraphicsContext restoreGraphicsState];
|
||||
return convert_imagerep_to_png(bmp, output_path);
|
||||
}
|
||||
|
||||
|
||||
static PyObject*
|
||||
bundle_image_as_png(PyObject *self UNUSED, PyObject *args, PyObject *kw) {@autoreleasepool {
|
||||
const char *b, *output_path = NULL; int is_identifier = 0; unsigned image_size = 256;
|
||||
static const char* kwlist[] = {"path_or_identifier", "output_path", "image_size", "is_identifier", NULL};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kw, "s|sIp", (char**)kwlist, &b, &output_path, &image_size, &is_identifier)) return NULL;
|
||||
NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; // autoreleased
|
||||
const char *b, *output_path = NULL; int image_type = 1; unsigned image_size = 256;
|
||||
static const char* kwlist[] = {"path_or_identifier", "output_path", "image_size", "image_type", NULL};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kw, "s|sIi", (char**)kwlist, &b, &output_path, &image_size, &image_type)) return NULL;
|
||||
NSImage *icon = nil;
|
||||
if (is_identifier) {
|
||||
NSURL *url = [workspace URLForApplicationWithBundleIdentifier:@(b)]; // autoreleased
|
||||
if (!url) {
|
||||
PyErr_Format(PyExc_KeyError, "Failed to find bundle path for identifier: %s", b); return NULL;
|
||||
}
|
||||
icon = [workspace iconForFile:@(url.fileSystemRepresentation)];
|
||||
} else icon = [workspace iconForFile:@(b)];
|
||||
switch (image_type) {
|
||||
case 0: case 1: {
|
||||
NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; // autoreleased
|
||||
if (image_type == 1) {
|
||||
NSURL *url = [workspace URLForApplicationWithBundleIdentifier:@(b)]; // autoreleased
|
||||
if (!url) {
|
||||
PyErr_Format(PyExc_KeyError, "Failed to find bundle path for identifier: %s", b); return NULL;
|
||||
}
|
||||
icon = [workspace iconForFile:@(url.fileSystemRepresentation)];
|
||||
} else icon = [workspace iconForFile:@(b)];
|
||||
} break;
|
||||
case 2:
|
||||
return render_emoji(@(b), image_size, output_path);
|
||||
default:
|
||||
if (@available(macOS 11.0, *)) {
|
||||
icon = [NSImage imageWithSystemSymbolName:@(b) accessibilityDescription:@""]; // autoreleased
|
||||
} else {
|
||||
PyErr_SetString(PyExc_ValueError, "Your version of macOS is too old to use symbol images, need >= 11.0"); return NULL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!icon) {
|
||||
PyErr_Format(PyExc_ValueError, "Failed to load icon for bundle: %s", b); return NULL;
|
||||
}
|
||||
return convert_image_to_png(icon, image_size, output_path);
|
||||
}}
|
||||
|
||||
static PyObject*
|
||||
symbol_image_as_png(PyObject *self UNUSED, PyObject *args, PyObject *kw) {@autoreleasepool {
|
||||
const char *b, *output_path = NULL; unsigned image_size = 256;
|
||||
static const char* kwlist[] = {"symbol", "output_path", "image_size", NULL};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kw, "s|sI", (char**)kwlist, &b, &output_path, &image_size)) return NULL;
|
||||
NSImage *img = [NSImage imageWithSystemSymbolName:@(b) accessibilityDescription:@""]; // autoreleased
|
||||
if (!img) {
|
||||
PyErr_Format(PyExc_KeyError, "Failed to find image for symbol name: %s", b); return NULL;
|
||||
}
|
||||
return convert_image_to_png(img, image_size, output_path);
|
||||
}}
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
|
||||
{"cocoa_set_global_shortcut", (PyCFunction)cocoa_set_global_shortcut, METH_VARARGS, ""},
|
||||
@@ -1134,7 +1159,6 @@ static PyMethodDef module_methods[] = {
|
||||
{"cocoa_set_app_icon", (PyCFunction)cocoa_set_app_icon, METH_VARARGS, ""},
|
||||
{"cocoa_set_dock_icon", (PyCFunction)cocoa_set_dock_icon, METH_VARARGS, ""},
|
||||
{"cocoa_bundle_image_as_png", (PyCFunction)(void(*)(void))bundle_image_as_png, METH_VARARGS | METH_KEYWORDS, ""},
|
||||
{"cocoa_symbol_image_as_png", (PyCFunction)(void(*)(void))symbol_image_as_png, METH_VARARGS | METH_KEYWORDS, ""},
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
|
||||
@@ -190,16 +190,16 @@ ssh_control_master_template = 'kssh-{kitty_pid}-{ssh_placeholder}'
|
||||
|
||||
# See https://specifications.freedesktop.org/icon-naming-spec/latest/ar01s04.html
|
||||
standard_icon_names = {
|
||||
'error': 'dialog-error',
|
||||
'warning': 'dialog-warning',
|
||||
'warn': 'dialog-warning',
|
||||
'info': 'dialog-information',
|
||||
'question': 'dialog-question',
|
||||
'error': ('dialog-error', '☠'),
|
||||
'warning': ('dialog-warning','⚠'),
|
||||
'warn': ('dialog-warning', '⚠'),
|
||||
'info': ('dialog-information', 'ℹ'),
|
||||
'question': ('dialog-question', '❔'),
|
||||
|
||||
'help': 'system-help',
|
||||
'file-manager': 'system-file-manager',
|
||||
'system-monitor': 'utilities-system-monitor',
|
||||
'text-editor': 'utilities-text-editor',
|
||||
'help': ('system-help', '📖'),
|
||||
'file-manager': ('system-file-manager', '🗄'),
|
||||
'system-monitor': ('utilities-system-monitor', '🎛'),
|
||||
'text-editor': ('utilities-text-editor', '📄'),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -571,8 +571,7 @@ def cocoa_send_notification(
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
def cocoa_bundle_image_as_png(path_or_identifier: str, output_path: str = '', image_size: int = 256, is_identifier: bool = False) -> bytes: ...
|
||||
def cocoa_symbol_image_as_png(symbol_name: str, output_path: str = '', image_size: int = 256) -> bytes: ...
|
||||
def cocoa_bundle_image_as_png(path_or_identifier: str, output_path: str = '', image_size: int = 256, image_type: int = 1) -> bytes: ...
|
||||
def cocoa_remove_delivered_notification(identifier: str) -> bool: ...
|
||||
def cocoa_live_delivered_notifications() -> bool: ...
|
||||
|
||||
|
||||
@@ -488,16 +488,20 @@ class MacOSIntegration(DesktopIntegration):
|
||||
return close_succeeded
|
||||
|
||||
def get_icon_for_name(self, name: str) -> str:
|
||||
from .fast_data_types import cocoa_bundle_image_as_png
|
||||
if name in self.failed_icons:
|
||||
return ''
|
||||
image_type, image_name = 1, name
|
||||
if sic := standard_icon_names.get(name):
|
||||
image_name = sic[1]
|
||||
image_type = 2
|
||||
icd = self.notification_manager.icon_data_cache
|
||||
icd_key = self.icd_key_prefix + name
|
||||
ans = icd.get_icon(icd_key)
|
||||
if ans:
|
||||
return ans
|
||||
from .fast_data_types import cocoa_bundle_image_as_png
|
||||
try:
|
||||
data = cocoa_bundle_image_as_png(name, is_identifier=True)
|
||||
data = cocoa_bundle_image_as_png(image_name, image_type=image_type)
|
||||
except Exception as err:
|
||||
if debug_desktop_integration:
|
||||
self.notification_manager.log(f'Failed to get icon for {name} with error: {err}')
|
||||
@@ -624,7 +628,7 @@ class FreeDesktopIntegration(DesktopIntegration):
|
||||
if nc.icon_names:
|
||||
for name in nc.icon_names:
|
||||
if sn := standard_icon_names.get(name):
|
||||
app_icon = sn
|
||||
app_icon = sn[0]
|
||||
break
|
||||
if icon_exists(name):
|
||||
app_icon = name
|
||||
|
||||
Reference in New Issue
Block a user