diff --git a/kitty/cocoa_window.m b/kitty/cocoa_window.m index 8aaf8eb73..9dbb67643 100644 --- a/kitty/cocoa_window.m +++ b/kitty/cocoa_window.m @@ -6,6 +6,7 @@ */ +#include "pytypedefs.h" #include "state.h" #include "cleanup.h" #include "cocoa_window.h" @@ -19,6 +20,9 @@ #include #include +static inline void cleanup_cfrelease(void *__p) { CFTypeRef *tp = (CFTypeRef *)__p; CFTypeRef cf = *tp; if (cf) { CFRelease(cf); } } +#define RAII_CoreFoundation(type, name, initializer) __attribute__((cleanup(cleanup_cfrelease))) type name = initializer + #if (MAC_OS_X_VERSION_MAX_ALLOWED < 101300) #define NSControlStateValueOn NSOnState #define NSControlStateValueOff NSOffState @@ -453,11 +457,11 @@ live_delivered_notifications(void) { } static void -schedule_notification(const char *appname, const char *identifier, const char *title, const char *body, const char *image_path, int urgency) { +schedule_notification(const char *appname, const char *identifier, const char *title, const char *body, const char *image_path, int urgency) {@autoreleasepool { UNUserNotificationCenter *center = get_notification_center_safely(); if (!center) return; // Configure the notification's payload. - UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init]; + RAII_CoreFoundation(UNMutableNotificationContent, *content, [[UNMutableNotificationContent alloc] init]); if (title) content.title = @(title); if (body) content.body = @(body); if (appname) content.threadIdentifier = @(appname); @@ -504,8 +508,7 @@ schedule_notification(const char *appname, const char *identifier, const char *t free(duped_ident); }); }]; - [content release]; -} +}} typedef struct { @@ -1069,6 +1072,40 @@ cocoa_set_uncaught_exception_handler(void) { NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler); } +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 + 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)]; + if (!icon) { + PyErr_Format(PyExc_ValueError, "Failed to load icon for bundle: %s", b); return NULL; + } + 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]); + RAII_CoreFoundation(NSBitmapImageRep, *rep, [[NSBitmapImageRep alloc] initWithCGImage:cg]); + NSData *png = [rep representationUsingType:NSBitmapImageFileTypePNG properties:@{NSImageCompressionFactor: @1.0}]; // autoreleased + if (output_path) { + if (![png writeToFile:@(output_path) atomically:YES]) { + PyErr_Format(PyExc_OSError, "Failed to write PNG data to %s", output_path); + return NULL; + } + return PyBytes_FromStringAndSize(NULL, 0); + } + return PyBytes_FromStringAndSize(png.bytes, png.length); +}} + static PyMethodDef module_methods[] = { {"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""}, {"cocoa_set_global_shortcut", (PyCFunction)cocoa_set_global_shortcut, METH_VARARGS, ""}, @@ -1079,6 +1116,7 @@ static PyMethodDef module_methods[] = { {"cocoa_set_url_handler", (PyCFunction)cocoa_set_url_handler, 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_bundle_image_as_png", (PyCFunction)(void(*)(void))bundle_image_as_png, METH_VARARGS | METH_KEYWORDS, ""}, {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index bdb990b6f..f38aea6f9 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -571,6 +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_remove_delivered_notification(identifier: str) -> bool: ... def cocoa_live_delivered_notifications() -> bool: ...