Bump rumps from 0.3.0 to 0.4.0 (#1896)

* Bump rumps from 0.3.0 to 0.4.0

Bumps [rumps](https://github.com/jaredks/rumps) from 0.3.0 to 0.4.0.
- [Release notes](https://github.com/jaredks/rumps/releases)
- [Changelog](https://github.com/jaredks/rumps/blob/master/CHANGES.rst)
- [Commits](https://github.com/jaredks/rumps/commits)

---
updated-dependencies:
- dependency-name: rumps
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update rumps==0.4.0

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com>

[skip ci]
This commit is contained in:
dependabot[bot] 2022-11-14 11:27:16 -08:00 committed by GitHub
parent b31d75aeee
commit 79cf61c53e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 573 additions and 243 deletions

View file

@ -1,38 +1,32 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# rumps: Ridiculously Uncomplicated macOS Python Statusbar apps.
# Copyright: (c) 2017, Jared Suttles. All rights reserved.
# License: BSD, see LICENSE for details.
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
_NOTIFICATIONS = True
# rumps: Ridiculously Uncomplicated macOS Python Statusbar apps.
# Copyright: (c) 2020, Jared Suttles. All rights reserved.
# License: BSD, see LICENSE for details.
# For compatibility with pyinstaller
# See: http://stackoverflow.com/questions/21058889/pyinstaller-not-finding-pyobjc-library-macos-python
import Foundation
import AppKit
try:
from Foundation import NSUserNotification, NSUserNotificationCenter
except ImportError:
_NOTIFICATIONS = False
from Foundation import (NSDate, NSTimer, NSRunLoop, NSDefaultRunLoopMode, NSSearchPathForDirectoriesInDomains,
NSMakeRect, NSLog, NSObject, NSMutableDictionary, NSString)
from AppKit import NSApplication, NSStatusBar, NSMenu, NSMenuItem, NSAlert, NSTextField, NSSecureTextField, NSImage, NSSlider, NSSize
NSMakeRect, NSLog, NSObject, NSMutableDictionary, NSString, NSUserDefaults)
from AppKit import NSApplication, NSStatusBar, NSMenu, NSMenuItem, NSAlert, NSTextField, NSSecureTextField, NSImage, NSSlider, NSSize, NSWorkspace, NSWorkspaceWillSleepNotification, NSWorkspaceDidWakeNotification
from PyObjCTools import AppHelper
import inspect
import os
import pickle
import sys
import traceback
import weakref
from collections import Mapping, Iterable
from .compat import text_type, string_types, iteritems, collections_abc
from .text_field import Editing, SecureEditing
from .utils import ListDict
from .compat import text_type, string_types, iteritems
from . import _internal
from . import events
from . import notifications
_TIMERS = weakref.WeakKeyDictionary()
separator = object()
@ -78,11 +72,13 @@ def alert(title=None, message='', ok=None, cancel=None, other=None, icon_path=No
message = message.replace('%', '%%')
if title is not None:
title = text_type(title)
_require_string_or_none(ok)
_internal.require_string_or_none(ok)
if not isinstance(cancel, string_types):
cancel = 'Cancel' if cancel else None
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(
title, ok, cancel, other, message)
if NSUserDefaults.standardUserDefaults().stringForKey_('AppleInterfaceStyle') == 'Dark':
alert.window().setAppearance_(AppKit.NSAppearance.appearanceNamed_('NSAppearanceNameVibrantDark'))
alert.setAlertStyle_(0) # informational style
if icon_path is not None:
icon = _nsimage_from_file(icon_path)
@ -91,119 +87,6 @@ def alert(title=None, message='', ok=None, cancel=None, other=None, icon_path=No
return alert.runModal()
def _gather_info_issue_9():
missing_plist = False
missing_bundle_ident = False
info_plist_path = os.path.join(os.path.dirname(sys.executable), 'Info.plist')
try:
with open(info_plist_path) as f:
import plistlib
try:
load_plist = plistlib.load
except AttributeError:
load_plist = plistlib.readPlist
try:
load_plist(f)['CFBundleIdentifier']
except Exception:
missing_bundle_ident = True
except IOError as e:
import errno
if e.errno == errno.ENOENT: # No such file or directory
missing_plist = True
info = '\n\n'
if missing_plist:
info += 'In this case there is no file at "%(info_plist_path)s"'
info += '\n\n'
confidence = 'should'
elif missing_bundle_ident:
info += 'In this case the file at "%(info_plist_path)s" does not contain a value for "CFBundleIdentifier"'
info += '\n\n'
confidence = 'should'
else:
confidence = 'may'
info += 'Running the following command %(confidence)s fix the issue:\n'
info += '/usr/libexec/PlistBuddy -c \'Add :CFBundleIdentifier string "rumps"\' %(info_plist_path)s\n'
return info % {'info_plist_path': info_plist_path, 'confidence': confidence}
def _default_user_notification_center():
notification_center = NSUserNotificationCenter.defaultUserNotificationCenter()
if notification_center is None:
info = (
'Failed to setup the notification center. This issue occurs when the "Info.plist" file '
'cannot be found or is missing "CFBundleIdentifier".'
)
try:
info += _gather_info_issue_9()
except Exception:
pass
raise RuntimeError(info)
else:
return notification_center
def notification(title, subtitle, message, data=None, sound=True, action_button=None, other_button=None,
has_reply_button=False, 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,
"The userInfo content must be of reasonable serialized size (less than 1k) or an exception will be thrown."
So don't do that!
:param title: text in a larger font.
:param subtitle: text in a smaller font below the `title`.
:param message: text representing the body of the notification below the `subtitle`.
: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 has_reply_button: whether or not the notification has a reply button.
:param icon: the filename of an image for the notification's icon, will replace the default.
"""
if not _NOTIFICATIONS:
raise RuntimeError('OS X 10.8+ is required to send notifications')
if data is not None and not isinstance(data, Mapping):
raise TypeError('notification data must be a mapping')
_require_string_or_none(title, subtitle, message)
notification = NSUserNotification.alloc().init()
notification.setTitle_(title)
notification.setSubtitle_(subtitle)
notification.setInformativeText_(message)
if data is not None:
app = getattr(App, '*app_instance')
dumped = app.serializer.dumps(data)
ns_dict = NSMutableDictionary.alloc().init()
ns_string = NSString.alloc().initWithString_(dumped)
ns_dict.setDictionary_({'value': ns_string})
notification.setUserInfo_(ns_dict)
if icon is not None:
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 has_reply_button:
notification.setHasReplyButton_(True)
notification.setDeliveryDate_(NSDate.dateWithTimeInterval_sinceDate_(0, NSDate.date()))
notification_center = _default_user_notification_center()
notification_center.scheduleNotification_(notification)
def application_support(name):
"""Return the application support folder path for the given `name`, creating it if it doesn't exist."""
app_support_path = os.path.join(NSSearchPathForDirectoriesInDomains(14, 1, 1).objectAtIndex_(0), name)
@ -248,18 +131,6 @@ def _nsimage_from_file(filename, dimensions=None, template=None):
return image
def _require_string(*objs):
for obj in objs:
if not isinstance(obj, string_types):
raise TypeError('a string is required but given {0}, a {1}'.format(obj, type(obj).__name__))
def _require_string_or_none(*objs):
for obj in objs:
if not(obj is None or isinstance(obj, string_types)):
raise TypeError('a string or None is required but given {0}, a {1}'.format(obj, type(obj).__name__))
# Decorators and helper function serving to register functions for dealing with interaction and events
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def timer(interval):
@ -365,36 +236,6 @@ def slider(*args, **options):
return f
return decorator
def notifications(f):
"""Decorator for registering a function to serve as a "notification center" for the application. This function will
receive the data associated with an incoming macOS notification sent using :func:`rumps.notification`. This occurs
whenever the user clicks on a notification for this application in the macOS Notification Center.
.. code-block:: python
@rumps.notifications
def notification_center(info):
if 'unix' in info:
print 'i know this'
"""
notifications.__dict__['*notification_center'] = f
return f
def _call_as_function_or_method(func, event):
# The idea here is that when using decorators in a class, the functions passed are not bound so we have to
# determine later if the functions we have (those saved as callbacks) for particular events need to be passed
# 'self'.
#
# This works for an App subclass method or a standalone decorated function. Will attempt to find function as
# a bound method of the App instance. If it is found, use it, otherwise simply call function.
app = getattr(App, '*app_instance')
for name, method in inspect.getmembers(app, predicate=inspect.ismethod):
if method.__func__ is func:
return method(event)
return func(event)
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -468,14 +309,14 @@ class Menu(ListDict):
menu.add(iterable)
return
for n, ele in enumerate(iteritems(iterable) if isinstance(iterable, Mapping) else iterable):
for n, ele in enumerate(iteritems(iterable) if isinstance(iterable, collections_abc.Mapping) else iterable):
# for mappings we recurse but don't drop down a level in the menu
if not isinstance(ele, MenuItem) and isinstance(ele, Mapping):
if not isinstance(ele, MenuItem) and isinstance(ele, collections_abc.Mapping):
parse_menu(ele, menu, depth)
# any iterables other than strings and MenuItems
elif not isinstance(ele, (string_types, MenuItem)) and isinstance(ele, Iterable):
elif not isinstance(ele, (string_types, MenuItem)) and isinstance(ele, collections_abc.Iterable):
try:
menuitem, submenu = ele
except TypeError:
@ -580,7 +421,7 @@ class MenuItem(Menu):
# NOTE:
# Because of the quirks of PyObjC, a class level dictionary **inside an NSObject subclass for 10.9.x** is required
# in order to have callback_ be a @classmethod. And we need callback_ to be class level because we can't use
# instances in setTarget_ method of NSMenuItem. Otherwise this would be much more straightfoward like Timer class.
# instances in setTarget_ method of NSMenuItem. Otherwise this would be much more straightforward like Timer class.
#
# So the target is always the NSApp class and action is always the @classmethod callback_ -- for every function
# decorated with @clicked(...). All we do is lookup the MenuItem instance and the user-provided callback function
@ -690,6 +531,35 @@ class MenuItem(Menu):
def state(self, new_state):
self._menuitem.setState_(new_state)
@property
def hidden(self):
"""Indicates whether the menu item is hidden.
.. versionadded:: 0.4.0
"""
return self._menuitem.isHidden()
@hidden.setter
def hidden(self, value):
self._menuitem.setHidden_(value)
def hide(self):
"""Hide the menu item.
.. versionadded:: 0.4.0
"""
self.hidden = True
def show(self):
"""Show the menu item.
.. versionadded:: 0.4.0
"""
self.hidden = False
def set_callback(self, callback, key=None):
"""Set the function serving as callback for when a click event occurs on this menu item. When `callback` is
``None``, it will disable the callback function and grey out the menu item. If `key` is a string, set as the
@ -703,7 +573,7 @@ class MenuItem(Menu):
:param callback: the function to be called when the user clicks on this menu item.
:param key: the key shortcut to click this menu item.
"""
_require_string_or_none(key)
_internal.require_string_or_none(key)
if key is not None:
self._menuitem.setKeyEquivalent_(key)
NSApp._ns_to_py_and_callback[self._menuitem] = self, callback
@ -744,7 +614,7 @@ class SliderMenuItem(object):
self._slider = NSSlider.alloc().init()
self._slider.setMinValue_(min_value)
self._slider.setMaxValue_(max_value)
self._slider.setValue_(value)
self._slider.setDoubleValue_(value)
self._slider.setFrameSize_(NSSize(*dimensions))
self._slider.setTarget_(NSApp)
self._menuitem = NSMenuItem.alloc().init()
@ -774,11 +644,11 @@ class SliderMenuItem(object):
@property
def value(self):
"""The current position of the slider."""
return self._slider.value()
return self._slider.doubleValue()
@value.setter
def value(self, new_value):
self._slider.setValue_(new_value)
self._slider.setDoubleValue_(new_value)
class SeparatorMenuItem(object):
@ -860,9 +730,9 @@ class Timer(object):
def callback_(self, _):
_log(self)
try:
return _call_as_function_or_method(getattr(self, '*callback'), self)
return _internal.call_as_function_or_method(getattr(self, '*callback'), self)
except Exception:
_log(traceback.format_exc())
traceback.print_exc()
class Window(object):
@ -899,7 +769,7 @@ class Window(object):
self._cancel = bool(cancel)
self._icon = None
_require_string_or_none(ok)
_internal.require_string_or_none(ok)
if not isinstance(cancel, string_types):
cancel = 'Cancel' if cancel else None
@ -908,9 +778,9 @@ class Window(object):
self._alert.setAlertStyle_(0) # informational style
if secure:
self._textfield = NSSecureTextField.alloc().initWithFrame_(NSMakeRect(0, 0, *dimensions))
self._textfield = SecureEditing.alloc().initWithFrame_(NSMakeRect(0, 0, *dimensions))
else:
self._textfield = NSTextField.alloc().initWithFrame_(NSMakeRect(0, 0, *dimensions))
self._textfield = Editing.alloc().initWithFrame_(NSMakeRect(0, 0, *dimensions))
self._textfield.setSelectable_(True)
self._alert.setAccessoryView_(self._textfield)
@ -982,7 +852,7 @@ class Window(object):
:param name: the text for a new button. Must be a string.
"""
_require_string(name)
_internal.require_string(name)
self._alert.addButtonWithTitle_(name)
def add_buttons(self, iterable=None, *args):
@ -1009,6 +879,8 @@ class Window(object):
:return: a :class:`rumps.rumps.Response` object that contains the text and the button clicked as an integer.
"""
_log(self)
if NSUserDefaults.standardUserDefaults().stringForKey_('AppleInterfaceStyle') == 'Dark':
self._alert.window().setAppearance_(AppKit.NSAppearance.appearanceNamed_('NSAppearanceNameVibrantDark'))
clicked = self._alert.runModal() % 999
if clicked > 2 and self._cancel:
clicked -= 1
@ -1062,27 +934,7 @@ class NSApp(NSObject):
_ns_to_py_and_callback = {}
def userNotificationCenter_didActivateNotification_(self, notification_center, notification):
notification_center.removeDeliveredNotification_(notification)
ns_dict = notification.userInfo()
if ns_dict is None:
data = None
else:
dumped = ns_dict['value']
app = getattr(App, '*app_instance')
data = app.serializer.loads(dumped)
try:
notification_function = getattr(notifications, '*notification_center')
except AttributeError: # notification center function not specified -> no error but warning in log
_log('WARNING: notification received but no function specified for answering it; use @notifications '
'decorator to register a function.')
else:
try:
data['activationType'] = notification.activationType()
data['actualDeliveryDate'] = notification.actualDeliveryDate()
_call_as_function_or_method(notification_function, data)
except Exception:
_log(traceback.format_exc())
notifications._clicked(notification_center, notification)
def initializeStatusBar(self):
self.nsstatusitem = NSStatusBar.systemStatusBar().statusItemWithLength_(-1) # variable dimensions
@ -1113,14 +965,42 @@ class NSApp(NSObject):
if not (self.nsstatusitem.title() or self.nsstatusitem.image()):
self.nsstatusitem.setTitle_(self._app['_name'])
def applicationDidFinishLaunching_(self, notification):
workspace = NSWorkspace.sharedWorkspace()
notificationCenter = workspace.notificationCenter()
notificationCenter.addObserver_selector_name_object_(
self,
self.receiveSleepNotification_,
NSWorkspaceWillSleepNotification,
None
)
notificationCenter.addObserver_selector_name_object_(
self,
self.receiveWakeNotification_,
NSWorkspaceDidWakeNotification,
None
)
def receiveSleepNotification_(self, ns_notification):
_log('receiveSleepNotification')
events.on_sleep.emit()
def receiveWakeNotification_(self, ns_notification):
_log('receiveWakeNotification')
events.on_wake.emit()
def applicationWillTerminate_(self, ns_notification):
_log('applicationWillTerminate')
events.before_quit.emit()
@classmethod
def callback_(cls, nsmenuitem):
self, callback = cls._ns_to_py_and_callback[nsmenuitem]
_log(self)
try:
return _call_as_function_or_method(callback, self)
return _internal.call_as_function_or_method(callback, self)
except Exception:
_log(traceback.format_exc())
traceback.print_exc()
class App(object):
@ -1150,7 +1030,7 @@ class App(object):
serializer = pickle
def __init__(self, name, title=None, icon=None, template=None, menu=None, quit_button='Quit'):
_require_string(name)
_internal.require_string(name)
self._name = name
self._icon = self._icon_nsimage = self._title = None
self._template = template
@ -1186,7 +1066,7 @@ class App(object):
@title.setter
def title(self, title):
_require_string_or_none(title)
_internal.require_string_or_none(title)
self._title = title
try:
self._nsapp.setStatusBarTitle()
@ -1308,13 +1188,7 @@ class App(object):
self._nsapp = NSApp.alloc().init()
self._nsapp._app = self.__dict__ # allow for dynamic modification based on this App instance
nsapplication.setDelegate_(self._nsapp)
if _NOTIFICATIONS:
try:
notification_center = _default_user_notification_center()
except RuntimeError:
pass
else:
notification_center.setDelegate_(self._nsapp)
notifications._init_nsapp(self._nsapp)
setattr(App, '*app_instance', self) # class level ref to running instance (for passing self to App subclasses)
t = b = None
@ -1326,4 +1200,6 @@ class App(object):
self._nsapp.initializeStatusBar()
AppHelper.installMachInterrupt()
events.before_start.emit()
AppHelper.runEventLoop()