Modernize the code used to interact with macOS

Recycle bin and notifications code. The old recycle bin code was
deprecated. Notifications support has been moved in-process.
This commit is contained in:
Kovid Goyal 2019-06-03 13:37:51 +05:30
parent b5230d9bd8
commit cff789f12c
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 132 additions and 49 deletions

View File

@ -53,27 +53,6 @@
#define NUKE(x) Py_XDECREF(x); x = NULL; #define NUKE(x) Py_XDECREF(x); x = NULL;
/* This function only works on 10.5 and later. Pass in a unicode object as path */
static PyObject* usbobserver_send2trash(PyObject *self, PyObject *args)
{
UInt8 *utf8_chars;
FSRef fp;
OSStatus op_result;
if (!PyArg_ParseTuple(args, "es", "utf-8", &utf8_chars)) {
return NULL;
}
FSPathMakeRefWithOptions(utf8_chars, kFSPathMakeRefDoNotFollowLeafSymlink, &fp, NULL);
op_result = FSMoveObjectToTrashSync(&fp, NULL, kFSFileOperationDefaultOptions);
PyMem_Free(utf8_chars);
if (op_result != noErr) {
PyErr_SetString(PyExc_OSError, GetMacOSStatusCommentString(op_result));
return NULL;
}
Py_RETURN_NONE;
}
static PyObject* static PyObject*
usbobserver_get_iokit_string_property(io_service_t dev, CFStringRef prop) { usbobserver_get_iokit_string_property(io_service_t dev, CFStringRef prop) {
@ -469,9 +448,6 @@ static PyMethodDef usbobserver_methods[] = {
{"get_mounted_filesystems", usbobserver_get_mounted_filesystems, METH_VARARGS, {"get_mounted_filesystems", usbobserver_get_mounted_filesystems, METH_VARARGS,
"Get mapping of mounted filesystems. Mapping is from BSD name to mount point." "Get mapping of mounted filesystems. Mapping is from BSD name to mount point."
}, },
{"send2trash", usbobserver_send2trash, METH_VARARGS,
"send2trash(unicode object) -> Send specified file/dir to trash"
},
{"user_locale", usbobserver_user_locale, METH_VARARGS, {"user_locale", usbobserver_user_locale, METH_VARARGS,
"user_locale() -> The name of the current user's locale or None if an error occurred" "user_locale() -> The name of the current user's locale or None if an error occurred"
}, },

View File

@ -9,7 +9,7 @@ __docformat__ = 'restructuredtext en'
import time import time
from calibre import prints from calibre import prints
from calibre.constants import islinux, isosx, get_osx_version, DEBUG, ispy3 from calibre.constants import islinux, isosx, get_osx_version, DEBUG, plugins
from polyglot.builtins import unicode_type from polyglot.builtins import unicode_type
@ -132,32 +132,15 @@ class DummyNotifier(Notifier):
class AppleNotifier(Notifier): class AppleNotifier(Notifier):
def __init__(self): def __init__(self):
self.ok = False self.cocoa, err = plugins['cocoa']
import os, sys self.ok = not err
try:
self.exe = os.path.join(sys.console_binaries_path.replace(
'console.app', 'calibre-notifier.app'), 'Calibre')
self.ok = os.access(self.exe, os.X_OK)
import subprocess
self.call = subprocess.Popen
except:
pass
def notify(self, body, summary): def notify(self, body, summary):
def encode(x):
if ispy3:
if isinstance(x, bytes):
x = x.decode('utf-8')
else:
if isinstance(x, unicode_type):
x = x.encode('utf-8')
return x
cmd = [self.exe, '-activate',
'net.kovidgoyal.calibre', '-message', encode(body)]
if summary: if summary:
cmd += ['-title', encode(summary)] title, informative_text = summary, body
self.call(cmd) else:
title, informative_text = body, None
self.cocoa.send_notification(None, title, informative_text)
def __call__(self, body, summary=None, replaces_id=None, timeout=0): def __call__(self, body, summary=None, replaces_id=None, timeout=0):
timeout, body, summary = self.get_msg_parms(timeout, body, summary) timeout, body, summary = self.get_msg_parms(timeout, body, summary)

View File

@ -7,6 +7,80 @@
#include <Cocoa/Cocoa.h> #include <Cocoa/Cocoa.h>
#include <string.h>
const char*
cocoa_send2trash(const char *utf8_path) {
NSString *path = [[NSString alloc] initWithUTF8String:utf8_path];
NSURL *url = [NSURL fileURLWithPath:path];
const char *ret = NULL;
NSError* ns_error = nil;
if (![[NSFileManager defaultManager] trashItemAtURL:url resultingItemURL:nil error:&ns_error]) {
ret = strdup([[ns_error localizedDescription] UTF8String]);
}
[url release];
[path release];
return ret;
}
extern void macos_notification_callback(const char*);
@interface NotificationDelegate : NSObject <NSUserNotificationCenterDelegate>
@end
void
cocoa_send_notification(const char *identifier, const char *title, const char *subtitle, const char *informativeText, const char* path_to_image) {
NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
if (!center) {return;}
if (!center.delegate) center.delegate = [[NotificationDelegate alloc] init];
NSUserNotification *n = [NSUserNotification new];
NSImage *img = nil;
if (path_to_image) {
NSString *p = [NSString stringWithUTF8String:path_to_image];
NSURL *url = [NSURL fileURLWithPath:p];
img = [[NSImage alloc] initWithContentsOfURL:url];
[url release]; [p release];
if (img) {
[n setValue:img forKey:@"_identityImage"];
[n setValue:@(false) forKey:@"_identityImageHasBorder"];
}
[img release];
}
#define SET(x) { \
if (x) { \
NSString *t = [NSString stringWithUTF8String:x]; \
n.x = t; \
[t release]; \
}}
SET(title); SET(subtitle); SET(informativeText);
#undef SET
if (identifier) {
n.userInfo = @{@"user_id": [NSString stringWithUTF8String:identifier]};
}
[center deliverNotification:n];
}
@implementation NotificationDelegate
- (void)userNotificationCenter:(NSUserNotificationCenter *)center
didDeliverNotification:(NSUserNotification *)notification {
(void)(center); (void)(notification);
}
- (BOOL) userNotificationCenter:(NSUserNotificationCenter *)center
shouldPresentNotification:(NSUserNotification *)notification {
(void)(center); (void)(notification);
return YES;
}
- (void) userNotificationCenter:(NSUserNotificationCenter *)center
didActivateNotification:(NSUserNotification *)notification {
(void)(center);
macos_notification_callback(notification.userInfo[@"user_id"] ? [notification.userInfo[@"user_id"] UTF8String] : NULL);
}
@end
double double

View File

@ -8,6 +8,10 @@
#include <Python.h> #include <Python.h>
extern double cocoa_cursor_blink_time(void); extern double cocoa_cursor_blink_time(void);
extern void cocoa_send_notification(const char *identitifer, const char *title, const char *subtitle, const char *informativeText, const char* path_to_image);
extern const char* cocoa_send2trash(const char *utf8_path);
static PyObject *notification_activated_callback = NULL;
static PyObject* static PyObject*
cursor_blink_time(PyObject *self) { cursor_blink_time(PyObject *self) {
@ -16,8 +20,54 @@ cursor_blink_time(PyObject *self) {
return PyFloat_FromDouble(ans); return PyFloat_FromDouble(ans);
} }
void
macos_notification_callback(const char* user_id) {
if (notification_activated_callback) {
PyObject *ret = PyObject_CallFunction(notification_activated_callback, "z", user_id);
if (ret == NULL) PyErr_Print();
else Py_DECREF(ret);
}
}
static PyObject*
set_notification_activated_callback(PyObject *self, PyObject *callback) {
(void)self;
if (notification_activated_callback) Py_DECREF(notification_activated_callback);
notification_activated_callback = callback;
Py_INCREF(callback);
Py_RETURN_NONE;
}
static PyObject*
send_notification(PyObject *self, PyObject *args) {
(void)self;
char *identifier = NULL, *title = NULL, *subtitle = NULL, *informativeText = NULL, *path_to_image = NULL;
if (!PyArg_ParseTuple(args, "zsz|zz", &identifier, &title, &informativeText, &path_to_image, &subtitle)) return NULL;
cocoa_send_notification(identifier, title, subtitle, informativeText, path_to_image);
Py_RETURN_NONE;
}
static PyObject*
send2trash(PyObject *self, PyObject *args) {
(void)self;
char *path = NULL;
if (!PyArg_ParseTuple(args, "s", &path)) return NULL;
const char *err = cocoa_send2trash(path);
if (err) {
PyErr_SetString(PyExc_OSError, err);
free((void*)err);
return NULL;
}
Py_RETURN_NONE;
}
static PyMethodDef module_methods[] = { static PyMethodDef module_methods[] = {
{"cursor_blink_time", (PyCFunction)cursor_blink_time, METH_NOARGS, ""}, {"cursor_blink_time", (PyCFunction)cursor_blink_time, METH_NOARGS, ""},
{"set_notification_activated_callback", (PyCFunction)set_notification_activated_callback, METH_O, ""},
{"send_notification", (PyCFunction)send_notification, METH_VARARGS, ""},
{"send2trash", (PyCFunction)send2trash, METH_VARARGS, ""},
{NULL, NULL, 0, NULL} /* Sentinel */ {NULL, NULL, 0, NULL} /* Sentinel */
}; };

View File

@ -89,7 +89,7 @@ if iswindows:
return delegate_recycle(path) return delegate_recycle(path)
elif isosx: elif isosx:
u = plugins['usbobserver'][0] u = plugins['cocoa'][0]
if hasattr(u, 'send2trash'): if hasattr(u, 'send2trash'):
def osx_recycle(path): def osx_recycle(path):
if isbytestring(path): if isbytestring(path):