mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-27 10:41:58 +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();
|
UNUserNotificationCenter *center = get_notification_center_safely();
|
||||||
if (!center) return;
|
if (!center) return;
|
||||||
// Configure the notification's payload.
|
// Configure the notification's payload.
|
||||||
RAII_CoreFoundation(UNMutableNotificationContent, *content, [[UNMutableNotificationContent alloc] init]);
|
UNMutableNotificationContent *content = [[[UNMutableNotificationContent alloc] init] autorelease];
|
||||||
if (title) content.title = @(title);
|
if (title) content.title = @(title);
|
||||||
if (body) content.body = @(body);
|
if (body) content.body = @(body);
|
||||||
if (appname) content.threadIdentifier = @(appname);
|
if (appname) content.threadIdentifier = @(appname);
|
||||||
@@ -1072,13 +1072,7 @@ cocoa_set_uncaught_exception_handler(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
convert_image_to_png(NSImage *icon, unsigned image_size, const char *output_path) {
|
convert_imagerep_to_png(NSBitmapImageRep *rep, 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
|
|
||||||
NSData *png = [rep representationUsingType:NSBitmapImageFileTypePNG properties:@{NSImageCompressionFactor: @1.0}]; // autoreleased
|
NSData *png = [rep representationUsingType:NSBitmapImageFileTypePNG properties:@{NSImageCompressionFactor: @1.0}]; // autoreleased
|
||||||
|
|
||||||
if (output_path) {
|
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);
|
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*
|
static PyObject*
|
||||||
bundle_image_as_png(PyObject *self UNUSED, PyObject *args, PyObject *kw) {@autoreleasepool {
|
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;
|
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", "is_identifier", NULL};
|
static const char* kwlist[] = {"path_or_identifier", "output_path", "image_size", "image_type", NULL};
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kw, "s|sIp", (char**)kwlist, &b, &output_path, &image_size, &is_identifier)) return NULL;
|
if (!PyArg_ParseTupleAndKeywords(args, kw, "s|sIi", (char**)kwlist, &b, &output_path, &image_size, &image_type)) return NULL;
|
||||||
NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; // autoreleased
|
|
||||||
NSImage *icon = nil;
|
NSImage *icon = nil;
|
||||||
if (is_identifier) {
|
switch (image_type) {
|
||||||
NSURL *url = [workspace URLForApplicationWithBundleIdentifier:@(b)]; // autoreleased
|
case 0: case 1: {
|
||||||
if (!url) {
|
NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; // autoreleased
|
||||||
PyErr_Format(PyExc_KeyError, "Failed to find bundle path for identifier: %s", b); return NULL;
|
if (image_type == 1) {
|
||||||
}
|
NSURL *url = [workspace URLForApplicationWithBundleIdentifier:@(b)]; // autoreleased
|
||||||
icon = [workspace iconForFile:@(url.fileSystemRepresentation)];
|
if (!url) {
|
||||||
} else icon = [workspace iconForFile:@(b)];
|
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) {
|
if (!icon) {
|
||||||
PyErr_Format(PyExc_ValueError, "Failed to load icon for bundle: %s", b); return NULL;
|
PyErr_Format(PyExc_ValueError, "Failed to load icon for bundle: %s", b); return NULL;
|
||||||
}
|
}
|
||||||
return convert_image_to_png(icon, image_size, output_path);
|
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[] = {
|
static PyMethodDef module_methods[] = {
|
||||||
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
|
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
|
||||||
{"cocoa_set_global_shortcut", (PyCFunction)cocoa_set_global_shortcut, METH_VARARGS, ""},
|
{"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_app_icon", (PyCFunction)cocoa_set_app_icon, METH_VARARGS, ""},
|
||||||
{"cocoa_set_dock_icon", (PyCFunction)cocoa_set_dock_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_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 */
|
{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
|
# See https://specifications.freedesktop.org/icon-naming-spec/latest/ar01s04.html
|
||||||
standard_icon_names = {
|
standard_icon_names = {
|
||||||
'error': 'dialog-error',
|
'error': ('dialog-error', '☠'),
|
||||||
'warning': 'dialog-warning',
|
'warning': ('dialog-warning','⚠'),
|
||||||
'warn': 'dialog-warning',
|
'warn': ('dialog-warning', '⚠'),
|
||||||
'info': 'dialog-information',
|
'info': ('dialog-information', 'ℹ'),
|
||||||
'question': 'dialog-question',
|
'question': ('dialog-question', '❔'),
|
||||||
|
|
||||||
'help': 'system-help',
|
'help': ('system-help', '📖'),
|
||||||
'file-manager': 'system-file-manager',
|
'file-manager': ('system-file-manager', '🗄'),
|
||||||
'system-monitor': 'utilities-system-monitor',
|
'system-monitor': ('utilities-system-monitor', '🎛'),
|
||||||
'text-editor': 'utilities-text-editor',
|
'text-editor': ('utilities-text-editor', '📄'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -571,8 +571,7 @@ def cocoa_send_notification(
|
|||||||
) -> None:
|
) -> None:
|
||||||
pass
|
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_bundle_image_as_png(path_or_identifier: str, output_path: str = '', image_size: int = 256, image_type: int = 1) -> bytes: ...
|
||||||
def cocoa_symbol_image_as_png(symbol_name: str, output_path: str = '', image_size: int = 256) -> bytes: ...
|
|
||||||
def cocoa_remove_delivered_notification(identifier: str) -> bool: ...
|
def cocoa_remove_delivered_notification(identifier: str) -> bool: ...
|
||||||
def cocoa_live_delivered_notifications() -> bool: ...
|
def cocoa_live_delivered_notifications() -> bool: ...
|
||||||
|
|
||||||
|
|||||||
@@ -488,16 +488,20 @@ class MacOSIntegration(DesktopIntegration):
|
|||||||
return close_succeeded
|
return close_succeeded
|
||||||
|
|
||||||
def get_icon_for_name(self, name: str) -> str:
|
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:
|
if name in self.failed_icons:
|
||||||
return ''
|
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 = self.notification_manager.icon_data_cache
|
||||||
icd_key = self.icd_key_prefix + name
|
icd_key = self.icd_key_prefix + name
|
||||||
ans = icd.get_icon(icd_key)
|
ans = icd.get_icon(icd_key)
|
||||||
if ans:
|
if ans:
|
||||||
return ans
|
return ans
|
||||||
from .fast_data_types import cocoa_bundle_image_as_png
|
|
||||||
try:
|
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:
|
except Exception as err:
|
||||||
if debug_desktop_integration:
|
if debug_desktop_integration:
|
||||||
self.notification_manager.log(f'Failed to get icon for {name} with error: {err}')
|
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:
|
if nc.icon_names:
|
||||||
for name in nc.icon_names:
|
for name in nc.icon_names:
|
||||||
if sn := standard_icon_names.get(name):
|
if sn := standard_icon_names.get(name):
|
||||||
app_icon = sn
|
app_icon = sn[0]
|
||||||
break
|
break
|
||||||
if icon_exists(name):
|
if icon_exists(name):
|
||||||
app_icon = name
|
app_icon = name
|
||||||
|
|||||||
Reference in New Issue
Block a user