-
Notifications
You must be signed in to change notification settings - Fork 177
/
notifications.py
262 lines (215 loc) · 8.53 KB
/
notifications.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# -*- coding: utf-8 -*-
_ENABLED = True
try:
from Foundation import NSUserNotification, NSUserNotificationCenter
except ImportError:
_ENABLED = False
import datetime
import os
import sys
import traceback
import Foundation
from . import _internal
from . import compat
from . import events
def on_notification(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'
"""
return events.on_notification.register(f)
def _gather_info_issue_9(): # pragma: no cover
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: # pragma: no cover
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 _init_nsapp(nsapp):
if _ENABLED:
try:
notification_center = _default_user_notification_center()
except RuntimeError:
pass
else:
notification_center.setDelegate_(nsapp)
@_internal.guard_unexpected_errors
def _clicked(ns_user_notification_center, ns_user_notification):
from . import rumps
ns_user_notification_center.removeDeliveredNotification_(ns_user_notification)
ns_dict = ns_user_notification.userInfo()
if ns_dict is None:
data = None
else:
dumped = ns_dict['value']
app = getattr(rumps.App, '*app_instance', rumps.App)
try:
data = app.serializer.loads(dumped)
except Exception:
traceback.print_exc()
return
# notification center function not specified => no error but log warning
if not events.on_notification.callbacks:
rumps._log(
'WARNING: notification received but no function specified for '
'answering it; use @notifications decorator to register a function.'
)
else:
notification = Notification(ns_user_notification, data)
events.on_notification.emit(notification)
def notify(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.
"""
from . import rumps
if not _ENABLED:
raise RuntimeError('OS X 10.8+ is required to send notifications')
_internal.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(rumps.App, '*app_instance', rumps.App)
dumped = app.serializer.dumps(data)
objc_string = _internal.string_to_objc(dumped)
ns_dict = Foundation.NSMutableDictionary.alloc().init()
ns_dict.setDictionary_({'value': objc_string})
notification.setUserInfo_(ns_dict)
if icon is not None:
notification.set_identityImage_(rumps._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_(Foundation.NSDate.dateWithTimeInterval_sinceDate_(0, Foundation.NSDate.date()))
notification_center = _default_user_notification_center()
notification_center.scheduleNotification_(notification)
class Notification(compat.collections_abc.Mapping):
def __init__(self, ns_user_notification, data):
self._ns = ns_user_notification
self._data = data
def __repr__(self):
return '<{0}: [data: {1}]>'.format(type(self).__name__, repr(self._data))
@property
def title(self):
return compat.text_type(self._ns.title())
@property
def subtitle(self):
return compat.text_type(self._ns.subtitle())
@property
def message(self):
return compat.text_type(self._ns.informativeText())
@property
def activation_type(self):
activation_type = self._ns.activationType()
if activation_type == 1:
return 'contents_clicked'
elif activation_type == 2:
return 'action_button_clicked'
elif activation_type == 3:
return 'replied'
elif activation_type == 4:
return 'additional_action_clicked'
@property
def delivered_at(self):
ns_date = self._ns.actualDeliveryDate()
seconds = ns_date.timeIntervalSince1970()
dt = datetime.datetime.fromtimestamp(seconds)
return dt
@property
def response(self):
ns_attributed_string = self._ns.response()
if ns_attributed_string is None:
return None
ns_string = ns_attributed_string.string()
return compat.text_type(ns_string)
@property
def data(self):
return self._data
def _check_if_mapping(self):
if not isinstance(self._data, compat.collections_abc.Mapping):
raise TypeError(
'notification cannot be used as a mapping when data is not a '
'mapping'
)
def __getitem__(self, key):
self._check_if_mapping()
return self._data[key]
def __iter__(self):
self._check_if_mapping()
return iter(self._data)
def __len__(self):
self._check_if_mapping()
return len(self._data)