From 8f527e8417aa8a44d24234667a7d0ef8a84d6996 Mon Sep 17 00:00:00 2001 From: Daniel Evans Date: Tue, 27 Nov 2018 21:05:44 -0800 Subject: [PATCH 1/2] commit changes from daredoes/rumps --- .gitignore | 2 ++ rumps/rumps.py | 61 +++++++++++++++++++++++++++++++++++++++++--------- rumps/utils.py | 14 +++++++----- 3 files changed, 62 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index f964bc0..adeccbf 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ dist/ *.py[co] .idea/ + +rumps\.egg-info/ diff --git a/rumps/rumps.py b/rumps/rumps.py index 82f3391..02fa2f3 100644 --- a/rumps/rumps.py +++ b/rumps/rumps.py @@ -14,7 +14,8 @@ from Foundation import (NSDate, NSTimer, NSRunLoop, NSDefaultRunLoopMode, NSSearchPathForDirectoriesInDomains, NSMakeRect, NSLog, NSObject) -from AppKit import NSApplication, NSStatusBar, NSMenu, NSMenuItem, NSAlert, NSTextField, NSImage +from AppKit import (NSApplication, NSStatusBar, NSMenu, NSMenuItem, NSAlert, NSTextField, NSSecureTextField, NSImage, + NSBeep, NSSpeechSynthesizer) from PyObjCTools import AppHelper import inspect @@ -40,9 +41,31 @@ def _log(*args): else: def _log(*_): pass + + debug_mode(False) +def error(): + """Generate a simple system beep.""" + NSBeep() + + +class SpeechSynthesizer: + + def __init__(self, message): + self.synth = NSSpeechSynthesizer.alloc().initWithVoice_(None) + self.synth.startSpeakingString_(message) + + def stop(self): + if self.synth.speaking: + self.synth.stopSpeaking() + + +def speak(message): + return SpeechSynthesizer(message) + + def alert(title=None, message='', ok=None, cancel=None, other=None, icon_path=None): """Generate a simple alert window. @@ -62,8 +85,8 @@ def alert(title=None, message='', ok=None, cancel=None, other=None, icon_path=No :param cancel: the text for the "cancel" button. If a string, the button will have that text. If `cancel` evaluates to ``True``, will create a button with text "Cancel". Otherwise, this button will not be created. - :param other: the text for the "other" button. If a string, the button will have that text. Otherwise, this button will not be - created. + :param other: the text for the "other" button. If a string, the button will have that text. Otherwise, this button + will not be created. :param icon_path: a path to an image. If ``None``, the applications icon is used. :return: a number representing the button pressed. The "ok" button is ``1`` and "cancel" is ``0``. """ @@ -137,7 +160,8 @@ def _default_user_notification_center(): return notification_center -def notification(title, subtitle, message, data=None, sound=True): +def notification(title, subtitle, message, data=None, sound=True, action_button=None, other_button=None, + reply_button=None, icon=None): """Send a notification to Notification Center (OS X 10.8+). If running on a version of macOS that does not support notifications, a ``RuntimeError`` will be raised. Apple says, @@ -151,6 +175,9 @@ def notification(title, subtitle, message, data=None, sound=True): :param data: will be passed to the application's "notification center" (see :func:`rumps.notifications`) when this notification is clicked. :param sound: whether the notification should make a noise when it arrives. + :param action_button: title for the action button. + :param other_button: title for the other button. + :param reply_button: A boolean representing whether or not the notification has a reply button. """ if not _NOTIFICATIONS: raise RuntimeError('OS X 10.8+ is required to send notifications') @@ -161,11 +188,21 @@ def notification(title, subtitle, message, data=None, sound=True): notification.setTitle_(title) notification.setSubtitle_(subtitle) notification.setInformativeText_(message) - infoDict = NSMutableDictionary.alloc().init() - infoDict.setDictionary_({} if data is None else data) - notification.setUserInfo_(infoDict) + info_dict = NSMutableDictionary.alloc().init() + info_dict.setDictionary_({} if data is None else data) + notification.setUserInfo_(info_dict) + if icon: + notification.set_identityImage_(_nsimage_from_file(icon)) if sound: notification.setSoundName_("NSUserNotificationDefaultSoundName") + if action_button: + notification.setActionButtonTitle_(action_button) + notification.set_showsButtons_(True) + if other_button: + notification.setOtherButtonTitle_(other_button) + notification.set_showsButtons_(True) + if reply_button: + notification.setHasReplyButton_(True) notification.setDeliveryDate_(NSDate.dateWithTimeInterval_sinceDate_(0, NSDate.date())) notification_center = _default_user_notification_center() notification_center.scheduleNotification_(notification) @@ -751,9 +788,10 @@ class Window(object): evaluates to ``True``, will create a button with text "Cancel". Otherwise, this button will not be created. :param dimensions: the size of the editable textbox. Must be sequence with a length of 2. + :param secure: should the text field be secured or not. With True the window can be used for passwords. """ - def __init__(self, message='', title='', default_text='', ok=None, cancel=None, dimensions=(320, 160)): + def __init__(self, message='', title='', default_text='', ok=None, cancel=None, dimensions=(320, 160), secure=None): message = text_type(message) message = message.replace('%', '%%') title = text_type(title) @@ -769,7 +807,8 @@ def __init__(self, message='', title='', default_text='', ok=None, cancel=None, title, ok, cancel, None, message) self._alert.setAlertStyle_(0) # informational style - self._textfield = NSTextField.alloc().initWithFrame_(NSMakeRect(0, 0, *dimensions)) + text_field_type = NSSecureTextField if secure else NSTextField + self._textfield = text_field_type.alloc().initWithFrame_(NSMakeRect(0, 0, *dimensions)) self._textfield.setSelectable_(True) self._alert.setAccessoryView_(self._textfield) @@ -929,6 +968,8 @@ def userNotificationCenter_didActivateNotification_(self, notification_center, n _log('WARNING: notification received but no function specified for answering it; use @notifications ' 'decorator to register a function.') else: + data['activationType'] = notification.activationType() + data['actualDeliveryDate'] = notification.actualDeliveryDate() _call_as_function_or_method(notification_function, data) def initializeStatusBar(self): @@ -1148,7 +1189,7 @@ def run(self, **options): debug_mode(debug) nsapplication = NSApplication.sharedApplication() - nsapplication.activateIgnoringOtherApps_(True) # NSAlerts in front + nsapplication.activateIgnoringOtherApps_(False) # NSAlerts in front self._nsapp = NSApp.alloc().init() self._nsapp._app = self.__dict__ # allow for dynamic modification based on this App instance nsapplication.setDelegate_(self._nsapp) diff --git a/rumps/utils.py b/rumps/utils.py index ea3620e..51d08d7 100644 --- a/rumps/utils.py +++ b/rumps/utils.py @@ -6,7 +6,9 @@ # License: BSD, see LICENSE for details. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - try: # Python 2.7+ - from collections import OrderedDict as _OrderedDict + from test.support import import_fresh_module + pyCollections = import_fresh_module('collections', blocked=['_collections']) + _OrderedDict, _Link = pyCollections.OrderedDict, pyCollections._Link except ImportError: from .packages.ordereddict import OrderedDict as _OrderedDict @@ -16,15 +18,17 @@ class ListDict(_OrderedDict): def __insertion(self, link_prev, key_value): key, value = key_value - if link_prev[2] != key: + if link_prev.key != key: if key in self: del self[key] - link_next = link_prev[1] - self._OrderedDict__map[key] = link_prev[1] = link_next[0] = [link_prev, link_next, key] + link_next = link_prev.next + new_link = _Link() + new_link.prev, new_link.next, new_link.key = link_prev, link_next, key + self._OrderedDict__map[key] = link_prev.next = link_next.prev = new_link dict.__setitem__(self, key, value) def insert_after(self, existing_key, key_value): self.__insertion(self._OrderedDict__map[existing_key], key_value) def insert_before(self, existing_key, key_value): - self.__insertion(self._OrderedDict__map[existing_key][0], key_value) + self.__insertion(self._OrderedDict__map[existing_key].prev, key_value) From 62fac6e895040313e252e1cba06842f98ca22e2b Mon Sep 17 00:00:00 2001 From: Daniel Evans Date: Sun, 2 Dec 2018 17:06:23 -0800 Subject: [PATCH 2/2] revert line changed for now unknown reasons --- rumps/rumps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rumps/rumps.py b/rumps/rumps.py index 1e7f1ed..fd81dc0 100644 --- a/rumps/rumps.py +++ b/rumps/rumps.py @@ -1195,7 +1195,7 @@ def run(self, **options): debug_mode(debug) nsapplication = NSApplication.sharedApplication() - nsapplication.activateIgnoringOtherApps_(False) # NSAlerts in front + nsapplication.activateIgnoringOtherApps_(True) # NSAlerts in front self._nsapp = NSApp.alloc().init() self._nsapp._app = self.__dict__ # allow for dynamic modification based on this App instance nsapplication.setDelegate_(self._nsapp)