version check infrastructure

This commit is contained in:
Kovid Goyal 2019-01-30 13:49:30 +05:30
parent 6a9a7dee55
commit c7a0626c69
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 117 additions and 7 deletions

View File

@ -115,6 +115,8 @@ get_dock_menu(id self UNUSED, SEL _cmd UNUSED, NSApplication *sender UNUSED) {
return dockMenu; return dockMenu;
} }
static PyObject *notification_activated_callback = NULL;
@interface NotificationDelegate : NSObject <NSUserNotificationCenterDelegate> @interface NotificationDelegate : NSObject <NSUserNotificationCenterDelegate>
@end @end
@ -133,13 +135,19 @@ get_dock_menu(id self UNUSED, SEL _cmd UNUSED, NSApplication *sender UNUSED) {
- (void) userNotificationCenter:(NSUserNotificationCenter *)center - (void) userNotificationCenter:(NSUserNotificationCenter *)center
didActivateNotification:(NSUserNotification *)notification { didActivateNotification:(NSUserNotification *)notification {
(void)(center); (void)(notification); (void)(center); (void)(notification);
if (notification_activated_callback) {
PyObject *ret = PyObject_CallFunction(notification_activated_callback, "z",
notification.identifier ? [notification.identifier UTF8String] : NULL);
if (ret == NULL) PyErr_Print();
else Py_DECREF(ret);
}
} }
@end @end
static PyObject* static PyObject*
cocoa_send_notification(PyObject *self UNUSED, PyObject *args) { cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
char *title = NULL, *subtitle = NULL, *message = NULL, *path_to_image = NULL; char *identifier = NULL, *title = NULL, *subtitle = NULL, *message = NULL, *path_to_image = NULL;
if (!PyArg_ParseTuple(args, "ssz|z", &title, &message, &path_to_image, &subtitle)) return NULL; if (!PyArg_ParseTuple(args, "zssz|z", &identifier, &title, &message, &path_to_image, &subtitle)) return NULL;
NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
if (!center) {PyErr_SetString(PyExc_RuntimeError, "Failed to get the user notification center"); return NULL; } if (!center) {PyErr_SetString(PyExc_RuntimeError, "Failed to get the user notification center"); return NULL; }
if (!center.delegate) center.delegate = [[NotificationDelegate alloc] init]; if (!center.delegate) center.delegate = [[NotificationDelegate alloc] init];
@ -151,6 +159,7 @@ cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
img = [[NSImage alloc] initWithContentsOfURL:url]; img = [[NSImage alloc] initWithContentsOfURL:url];
[url release]; [p release]; [url release]; [p release];
} }
n.identifier = identifier ? [NSString stringWithUTF8String:identifier] : nil;
n.title = title ? [NSString stringWithUTF8String:title] : nil; n.title = title ? [NSString stringWithUTF8String:title] : nil;
n.subtitle = subtitle ? [NSString stringWithUTF8String:subtitle] : nil; n.subtitle = subtitle ? [NSString stringWithUTF8String:subtitle] : nil;
n.informativeText = message ? [NSString stringWithUTF8String:message] : nil; n.informativeText = message ? [NSString stringWithUTF8String:message] : nil;
@ -159,6 +168,7 @@ cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
[n setValue:@(false) forKey:@"_identityImageHasBorder"]; [n setValue:@(false) forKey:@"_identityImageHasBorder"];
} }
[center deliverNotification:n]; [center deliverNotification:n];
if (n.identifier) { [n.identifier release]; n.identifier = nil; }
if (n.title) { [n.title release]; n.title = nil; } if (n.title) { [n.title release]; n.title = nil; }
if (n.subtitle) { [n.subtitle release]; n.subtitle = nil; } if (n.subtitle) { [n.subtitle release]; n.subtitle = nil; }
if (n.informativeText) { [n.informativeText release]; n.informativeText = nil; } if (n.informativeText) { [n.informativeText release]; n.informativeText = nil; }
@ -167,6 +177,41 @@ cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static void
SIGTERM_handler(int signum UNUSED) {
exit(EXIT_FAILURE);
}
static void
call_timer_callback(PyObject *timer_callback) {
PyObject *ret = PyObject_CallObject(timer_callback, NULL);
if (ret == NULL) PyErr_Print();
else Py_DECREF(ret);
}
static PyObject*
cocoa_run_notification_loop(PyObject *self UNUSED, PyObject *args) {
PyObject *timer_callback;
double timeout;
if (!PyArg_ParseTuple(args, "OOd", &notification_activated_callback, &timer_callback, &timeout)) return NULL;
signal(SIGTERM, SIGTERM_handler);
signal(SIGINT, SIGTERM_handler);
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSApplication * application = [NSApplication sharedApplication];
// prevent icon in dock
[application setActivationPolicy:NSApplicationActivationPolicyAccessory];
// timer will fire after timeout, so fire it once at the start
call_timer_callback(timer_callback);
[NSTimer scheduledTimerWithTimeInterval:timeout
repeats:YES
block:^(NSTimer *timer UNUSED) {
call_timer_callback(timer_callback);
}];
[application run];
[pool drain];
return 0;
}
void void
cocoa_create_global_menu(void) { cocoa_create_global_menu(void) {
NSString* app_name = find_app_name(); NSString* app_name = find_app_name();
@ -376,6 +421,7 @@ static PyMethodDef module_methods[] = {
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""}, {"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
{"cocoa_set_new_window_trigger", (PyCFunction)cocoa_set_new_window_trigger, METH_VARARGS, ""}, {"cocoa_set_new_window_trigger", (PyCFunction)cocoa_set_new_window_trigger, METH_VARARGS, ""},
{"cocoa_send_notification", (PyCFunction)cocoa_send_notification, METH_VARARGS, ""}, {"cocoa_send_notification", (PyCFunction)cocoa_send_notification, METH_VARARGS, ""},
{"cocoa_run_notification_loop", (PyCFunction)cocoa_run_notification_loop, METH_VARARGS, ""},
{NULL, NULL, 0, NULL} /* Sentinel */ {NULL, NULL, 0, NULL} /* Sentinel */
}; };

View File

@ -3,20 +3,76 @@
# License: GPLv3 Copyright: 2019, Kovid Goyal <kovid at kovidgoyal.net> # License: GPLv3 Copyright: 2019, Kovid Goyal <kovid at kovidgoyal.net>
import os
import subprocess import subprocess
import time
from urllib.request import urlopen
from .constants import is_macos, logo_png_file from .constants import cache_dir, is_macos, logo_png_file, version
from .utils import open_url
CHANGELOG_URL = 'https://sw.kovidgoyal.net/kitty/changelog.html'
RELEASED_VERSION_URL = 'https://sw.kovidgoyal.net/kitty/current-version.txt'
CHECK_INTERVAL = 24 * 60 * 60
def version_notification_log():
return os.path.join(cache_dir(), 'new-version-notifications.txt')
def notify_new_version(version):
notify('kitty update available!', 'kitty version {} released'.format('.'.join(map(str, version))))
def get_released_version():
try:
raw = urlopen(RELEASED_VERSION_URL).read().decode('utf-8').strip()
except Exception:
raw = '0.0.0'
return tuple(map(int, raw.split('.')))
notified_versions = set()
def save_notification(version):
notified_versions.add(version)
version = '.'.join(map(str, version))
with open(version_notification_log(), 'a') as f:
print(version, file=f)
def already_notified(version):
if not hasattr(already_notified, 'read_cache'):
already_notified.read_cache = True
with open(version_notification_log()) as f:
for line in f:
notified_versions.add(tuple(map(int, line.strip().split('.'))))
return tuple(version) in notified_versions
if is_macos: if is_macos:
from .fast_data_types import cocoa_send_notification from .fast_data_types import cocoa_send_notification, cocoa_run_notification_loop
def notify(title, body, timeout=5000, application='kitty', icon=True): def notify(title, body, timeout=5000, application='kitty', icon=True, identifier=None):
if icon is True: if icon is True:
icon = None icon = None
cocoa_send_notification(title, body, icon) cocoa_send_notification(identifier, title, body, icon)
def notification_activated(notification_identifier):
open_url(CHANGELOG_URL)
def do_check():
new_version = get_released_version()
if new_version > version and not already_notified(new_version):
save_notification(new_version)
notify_new_version(new_version)
def update_check():
cocoa_run_notification_loop(notification_activated, do_check, CHECK_INTERVAL)
else: else:
def notify(title, body, timeout=5000, application='kitty', icon=True): def notify(title, body, timeout=5000, application='kitty', icon=True, identifier=None):
cmd = ['notify-send', '-t', str(timeout), '-a', application] cmd = ['notify-send', '-t', str(timeout), '-a', application]
if icon is True: if icon is True:
icon = logo_png_file icon = logo_png_file
@ -24,3 +80,11 @@ else:
cmd.extend(['-i', icon]) cmd.extend(['-i', icon])
subprocess.Popen( subprocess.Popen(
cmd + [title, body], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL) cmd + [title, body], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL)
def update_check():
while True:
new_version = get_released_version()
if new_version > version and not already_notified(new_version):
save_notification(new_version)
notify_new_version(new_version)
time.sleep(CHECK_INTERVAL)