├── .github
└── FUNDING.yml
├── LICENSE
├── NCutil.py
└── README.md
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 |
2 | github: [jacobsalmela]
3 | custom: ['https://jacobsalmela.com/sponsor/']
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 | {description}
294 | Copyright (C) {year} {fullname}
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | {signature of Ty Coon}, 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
341 |
--------------------------------------------------------------------------------
/NCutil.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # NCutil is free software: you can redistribute it and/or modify
3 | # it under the terms of the GNU General Public License as published by
4 | # the Free Software Foundation, either version 2 of the License, or
5 | # (at your option) any later version.
6 | ##############################
7 | ######### IMPORTS ############
8 | import sys
9 | import argparse
10 | import os
11 | import subprocess
12 | import sqlite3
13 |
14 | from platform import mac_ver
15 | from glob import glob
16 |
17 | # even though this is not ideal Python form, we're moving these
18 | # imports into the functions that need them since these are slow
19 | # to import
20 | #from Foundation import NSBundle, NSFileManager
21 | #from AppKit import NSWorkspace
22 |
23 | ##############################
24 | ######## FUNCTIONS ###########
25 | def usage():
26 | return """
27 | (c) 2015 by Jacob Salmela. http://jacobsalmela.com
28 | Modify OS X's Notification Center from the command line
29 | """
30 |
31 |
32 | def get_osx_major():
33 | '''Return OS X version in format of 10.x.x'''
34 | v, _, _ = mac_ver()
35 | #Parse Out Major Version, mac_ver() can produce 10.10.2, 10.9.5, 10.8..
36 | return v.split('.')[0] + "." + v.split('.')[1]
37 |
38 |
39 | def get_nc_db():
40 | '''Returns a path to the current (hopefully?) NotificationCenter db'''
41 | nc_db = None
42 | osx_major = get_osx_major()
43 | if osx_major in ['10.8', '10.9']:
44 | nc_nb_path = os.path.expanduser(
45 | '~/Library/Application Support/NotificationCenter/')
46 | nc_dbs = glob(nc_nb_path + '*.db')
47 | if nc_dbs:
48 | nc_dbs.sort(key=os.path.getmtime)
49 | # most recently modified will be the last one
50 | nc_db = nc_dbs[-1]
51 | # Support for osx 10.10 added via randomly generated id for
52 | # Notification Center Database
53 | elif osx_major in ['10.10', '10.11', '10.12', '10.13']:
54 | darwin_user_dir = subprocess.check_output(
55 | ['/usr/bin/getconf', 'DARWIN_USER_DIR']).rstrip()
56 | nc_db = os.path.join(
57 | darwin_user_dir, 'com.apple.notificationcenter/db/db')
58 | else:
59 | raise Exception('Unsupported macOS version; unable to locate Notification Center database')
60 | return nc_db
61 |
62 |
63 | def connect_to_db():
64 | '''Connect to the Notification Center db and return connection object
65 | and cursor'''
66 | conn = None
67 | curs = None
68 | #Connect To SQLLite
69 | nc_db = get_nc_db()
70 | if nc_db:
71 | conn = sqlite3.connect(nc_db)
72 | conn.text_factory = str
73 | curs = conn.cursor()
74 | return conn, curs
75 |
76 |
77 | def kill_notification_center():
78 | '''Send a kill signal to NotificationCenter and usernoted; they will
79 | relaunch'''
80 | subprocess.call(['/usr/bin/killall', 'NotificationCenter'])
81 | subprocess.call(['/usr/bin/killall', 'usernoted'])
82 |
83 |
84 | def commit_changes(conn):
85 | '''Apply the changes and close the sqlite connection'''
86 | conn.commit()
87 | conn.close()
88 |
89 |
90 | def verboseOutput(*args):
91 | #------------------------
92 | if verbose:
93 | try:
94 | print "Verbose:", args
95 | except:
96 | pass
97 |
98 |
99 | def list_clients():
100 | '''List all bundleids in database'''
101 | conn, curs = connect_to_db()
102 | curs.execute("select bundleid from app_info")
103 | for row in curs.fetchall():
104 | print row[0]
105 | conn.close()
106 |
107 |
108 | def get_available_id(curs):
109 | '''Get the highest app_id, then increment'''
110 | curs.execute("select app_id from app_info")
111 | # return first field of last row
112 | last_id = curs.fetchall()[-1][0]
113 | return last_id + 1
114 |
115 |
116 | def insert_app(bundle_ids):
117 | '''Adds bundle_ids to Notification Center database'''
118 | conn, curs = connect_to_db()
119 | for bundle_id in bundle_ids:
120 | if not bundleid_exists(bundle_id):
121 | next_id = get_available_id(curs)
122 | curs.execute("INSERT INTO app_info VALUES('%s', '%s', '14', '5', '%s')"
123 | % (next_id, bundle_id, next_id))
124 | else:
125 | print >> sys.stderr, "%s is already in Notification Center" % bundle_id
126 |
127 | commit_changes(conn)
128 | kill_notification_center()
129 |
130 |
131 | def remove_app(bundle_ids):
132 | '''Removes bundle_ids from Notification Center database'''
133 | conn, curs = connect_to_db()
134 | for bundle_id in bundle_ids:
135 | if not bundleid_exists(bundle_id):
136 | print >> sys.stderr, (
137 | "WARNING: %s not in Notification Center" % bundle_id)
138 | else:
139 | curs.execute("DELETE from app_info where bundleid IS '%s'" % (bundle_id))
140 |
141 | commit_changes(conn)
142 | kill_notification_center()
143 |
144 |
145 | def set_flags(flags, bundle_id):
146 | '''Sets Notification Center flags for bundle_id'''
147 | conn, curs = connect_to_db()
148 | curs.execute("UPDATE app_info SET flags='%s' where bundleid='%s'"
149 | % (flags, bundle_id))
150 | commit_changes(conn)
151 |
152 |
153 | def bundleid_exists(bundle_id):
154 | '''Returns a boolean telling us if the bundle_id is in the database.'''
155 | conn, curs = connect_to_db()
156 | curs.execute("SELECT bundleid from app_info WHERE bundleid IS '%s'"
157 | % bundle_id)
158 | matching_ids = [row[0] for row in curs.fetchall()]
159 | conn.close()
160 | return len(matching_ids) > 0
161 |
162 |
163 | def get_matching_ids(match_string):
164 | '''Returns any bundle_ids matching the match_string'''
165 | conn, curs = connect_to_db()
166 | curs.execute("SELECT bundleid from app_info WHERE bundleid LIKE '%s'"
167 | % match_string)
168 | matching_ids = [row[0] for row in curs.fetchall()]
169 | conn.close()
170 | return matching_ids
171 |
172 |
173 | def get_flags(bundle_id):
174 | '''Returns flags for bundle_id'''
175 | conn, curs = connect_to_db()
176 | curs.execute("SELECT flags from app_info where bundleid='%s'" % (bundle_id))
177 | try:
178 | flags = curs.fetchall()[0][0]
179 | except IndexError:
180 | flags = 0
181 | conn.close()
182 | return int(flags)
183 |
184 |
185 | def get_show_count(bundle_id):
186 | '''Returns number of items to show in Notification Center for bundle_id'''
187 | conn, curs = connect_to_db()
188 | curs.execute("SELECT show_count from app_info where bundleid='%s'" % (bundle_id))
189 | try:
190 | flags = curs.fetchall()[0][0]
191 | except IndexError:
192 | flags = 0
193 | conn.close()
194 | return int(flags)
195 |
196 |
197 | def remove_system_center():
198 | '''Sets alert style to 'none'' for all bundle_ids starting with
199 | _SYSTEM_CENTER_:. Not convinced this is a great idea, but there it is...'''
200 | set_alert('none', get_matching_ids('_SYSTEM_CENTER_:%'))
201 |
202 |
203 | def get_app_name(bundle_id):
204 | '''Get display name for app specified by bundle_id'''
205 | from AppKit import NSWorkspace
206 | from Foundation import NSFileManager
207 | app_path = NSWorkspace.sharedWorkspace(
208 | ).absolutePathForAppBundleWithIdentifier_(bundle_id)
209 | if app_path:
210 | return NSFileManager.defaultManager().displayNameAtPath_(app_path)
211 | return bundle_id
212 |
213 |
214 | def get_bundle_id(app_name):
215 | '''Given an app_name, get the bundle_id'''
216 | from AppKit import NSWorkspace
217 | from Foundation import NSBundle
218 | app_path = NSWorkspace.sharedWorkspace(
219 | ).fullPathForApplication_(app_name)
220 | if app_path:
221 | return NSBundle.bundleWithPath_(app_path).bundleIdentifier()
222 | return None
223 |
224 |
225 | # flags are bits in a 16 bit(?) data structure
226 | DONT_SHOW_IN_CENTER = 1 << 0
227 | BADGE_ICONS = 1 << 1
228 | SOUNDS = 1 << 2
229 | BANNER_STYLE = 1 << 3
230 | ALERT_STYLE = 1 << 4
231 | UNKNOWN_5 = 1 << 5
232 | UNKNOWN_6 = 1 << 6
233 | UNKNOWN_7 = 1 << 7
234 | UNKNOWN_8 = 1 << 8
235 | UNKNOWN_9 = 1 << 9
236 | UNKNOWN_10 = 1 << 10
237 | UNKNOWN_11 = 1 << 11
238 | SUPPRESS_NOTIFICATIONS_ON_LOCKSCREEN = 1 << 12
239 | SHOW_PREVIEWS_ALWAYS = 1 << 13
240 | SUPPRESS_MESSAGE_PREVIEWS = 1 << 14
241 | UNKNOWN_15 = 1 << 15
242 |
243 |
244 | def error_and_exit_if_not_bundle_exists(bundle_id):
245 | '''Print an error and exit if bundle_id doesn't exist.'''
246 | if not bundleid_exists(bundle_id):
247 | print >> sys.stderr, "%s not in Notification Center" % bundle_id
248 | exit(1)
249 |
250 |
251 | def get_alert_style(bundle_id):
252 | '''Print the alert style for bundle_id'''
253 | error_and_exit_if_not_bundle_exists(bundle_id)
254 | current_flags = get_flags(bundle_id)
255 | if current_flags & ALERT_STYLE:
256 | print "alerts"
257 | elif current_flags & BANNER_STYLE:
258 | print "banners"
259 | else:
260 | print "none"
261 |
262 |
263 | def get_show_on_lock_screen(bundle_id):
264 | '''Print state of "Show notifications on lock screen"'''
265 | error_and_exit_if_not_bundle_exists(bundle_id)
266 | current_flags = get_flags(bundle_id)
267 | if current_flags & SUPPRESS_NOTIFICATIONS_ON_LOCKSCREEN:
268 | print 'false'
269 | else:
270 | print 'true'
271 |
272 |
273 | def get_badge_app_icon(bundle_id):
274 | '''Print state of "Badge app icon"'''
275 | error_and_exit_if_not_bundle_exists(bundle_id)
276 | current_flags = get_flags(bundle_id)
277 | if current_flags & BADGE_ICONS:
278 | print 'true'
279 | else:
280 | print 'false'
281 |
282 |
283 | def get_notification_sound(bundle_id):
284 | '''Print state of "Play sound for notifications"'''
285 | error_and_exit_if_not_bundle_exists(bundle_id)
286 | current_flags = get_flags(bundle_id)
287 | if current_flags & SOUNDS:
288 | print 'true'
289 | else:
290 | print 'false'
291 |
292 |
293 | def get_show_in_notification_center(bundle_id):
294 | '''Print state of "Show in Notification Center"'''
295 | error_and_exit_if_not_bundle_exists(bundle_id)
296 | current_flags = get_flags(bundle_id)
297 | if current_flags & DONT_SHOW_IN_CENTER:
298 | print 'false'
299 | else:
300 | show_count = get_show_count(bundle_id)
301 | if show_count == 1:
302 | items = '1 recent item'
303 | else:
304 | items = '%s recent items' % show_count
305 | print items
306 |
307 |
308 | def get_info(bundle_id):
309 | '''Print the Notification Center settings for bundle_id'''
310 | error_and_exit_if_not_bundle_exists(bundle_id)
311 | current_flags = get_flags(bundle_id)
312 | if current_flags & ALERT_STYLE:
313 | style = "Alerts"
314 | elif current_flags & BANNER_STYLE:
315 | style = "Banners"
316 | else:
317 | style = "None"
318 | app_name = get_app_name(bundle_id)
319 | print "Notification Center settings for %s:" % app_name
320 | print ' %-34s %s' % (app_name + ' alert style:', style)
321 | if current_flags & SUPPRESS_NOTIFICATIONS_ON_LOCKSCREEN:
322 | show_notifications_on_lock_screen = False
323 | print " Show notifications on lock screen: No"
324 | else:
325 | show_notifications_on_lock_screen = True
326 | print " Show notifications on lock screen: Yes"
327 | if current_flags & SUPPRESS_MESSAGE_PREVIEWS:
328 | print " Show message preview: Off"
329 | elif ((current_flags & SHOW_PREVIEWS_ALWAYS)
330 | and show_notifications_on_lock_screen):
331 | print " Show message preview: Always"
332 | if current_flags & DONT_SHOW_IN_CENTER:
333 | print " Show in Notification Center: No"
334 | else:
335 | show_count = get_show_count(bundle_id)
336 | if show_count == 1:
337 | items = '1 Recent Item'
338 | else:
339 | items = '%s Recent Items' % show_count
340 | print " Show in Notification Center: %s" % items
341 | if current_flags & BADGE_ICONS:
342 | print " Badge app icon: Yes"
343 | else:
344 | print " Badge app icon: No"
345 | if current_flags & SOUNDS:
346 | print " Play sound for notifications: Yes"
347 | else:
348 | print " Play sound for notifications: No"
349 |
350 |
351 | def verify_value_in_allowed(label, value, allowed_values):
352 | '''Make sure our value has an allowed value'''
353 | if value not in allowed_values:
354 | print >> sys.stderr, (
355 | "%s must be one of: %s."
356 | % (label, ', '.join(allowed_values)))
357 | exit(1)
358 |
359 |
360 | def bundle_ids_or_error_and_exit(bundle_ids):
361 | '''Print an error and exit if bundle_ids is empty'''
362 | if not bundle_ids:
363 | print >> sys.stderr, "Must specify at least one bundle id!"
364 | exit(1)
365 |
366 |
367 | def set_alert(style, bundle_ids):
368 | '''Set the alert style for bundle_id. If kill_nc is False, skip killing
369 | the NotificationCenter and usernoted processes'''
370 |
371 | # verify this is a supported alert type
372 | verify_value_in_allowed('Alert style', style, ['none', 'alerts', 'banners'])
373 | # exit if no bundle_ids were provided
374 | bundle_ids_or_error_and_exit(bundle_ids)
375 | for bundle_id in bundle_ids:
376 | if not bundleid_exists(bundle_id):
377 | print >> sys.stderr, (
378 | "WARNING: %s not in Notification Center" % bundle_id)
379 | else:
380 | current_flags = get_flags(bundle_id)
381 | # turn off both banner and alert flags
382 | new_flags = current_flags & ~(BANNER_STYLE | ALERT_STYLE)
383 | if style == 'alerts':
384 | # turn on alert flag
385 | new_flags = new_flags | ALERT_STYLE
386 | elif style == 'banners':
387 | # turn on banner flag
388 | new_flags = new_flags | BANNER_STYLE
389 | if new_flags != current_flags:
390 | set_flags(new_flags, bundle_id)
391 | kill_notification_center()
392 |
393 |
394 | def set_show_on_lock_screen(value, bundle_ids):
395 | '''Set the boolean value for badging the app icon'''
396 |
397 | # verify this is a supported value
398 | verify_value_in_allowed(
399 | 'Show on lock screen value', value, ['true', 'false'])
400 | # exit if no bundle_ids were provided
401 | bundle_ids_or_error_and_exit(bundle_ids)
402 | for bundle_id in bundle_ids:
403 | if not bundleid_exists(bundle_id):
404 | print >> sys.stderr, (
405 | "WARNING: %s not in Notification Center" % bundle_id)
406 | else:
407 | current_flags = get_flags(bundle_id)
408 | if value == 'true':
409 | new_flags = (
410 | current_flags & ~SUPPRESS_NOTIFICATIONS_ON_LOCKSCREEN)
411 | else:
412 | new_flags = current_flags | SUPPRESS_NOTIFICATIONS_ON_LOCKSCREEN
413 | if new_flags != current_flags:
414 | set_flags(new_flags, bundle_id)
415 | kill_notification_center()
416 |
417 |
418 | def set_badge_app_icon(value, bundle_ids):
419 | '''Set the boolean value for causing the notifications to be displayed
420 | when the screen is locked'''
421 |
422 | # verify this is a supported value
423 | verify_value_in_allowed(
424 | 'Badge app icon value', value, ['true', 'false'])
425 | # exit if no bundle_ids were provided
426 | bundle_ids_or_error_and_exit(bundle_ids)
427 | for bundle_id in bundle_ids:
428 | if not bundleid_exists(bundle_id):
429 | print >> sys.stderr, (
430 | "WARNING: %s not in Notification Center" % bundle_id)
431 | else:
432 | current_flags = get_flags(bundle_id)
433 | if value == 'true':
434 | new_flags = current_flags | BADGE_ICONS
435 | else:
436 | new_flags = current_flags & ~BADGE_ICONS
437 | if new_flags != current_flags:
438 | set_flags(new_flags, bundle_id)
439 | kill_notification_center()
440 |
441 |
442 | def set_notification_sound(value, bundle_ids):
443 | '''Set the boolean value for notification sound'''
444 |
445 | # verify this is a supported value
446 | verify_value_in_allowed(
447 | 'Notification sound value', value, ['true', 'false'])
448 | # exit if no bundle_ids were provided
449 | bundle_ids_or_error_and_exit(bundle_ids)
450 | for bundle_id in bundle_ids:
451 | if not bundleid_exists(bundle_id):
452 | print >> sys.stderr, (
453 | "WARNING: %s not in Notification Center" % bundle_id)
454 | else:
455 | current_flags = get_flags(bundle_id)
456 | if value == 'true':
457 | new_flags = current_flags | SOUNDS
458 | else:
459 | new_flags = current_flags & ~SOUNDS
460 | if new_flags != current_flags:
461 | set_flags(new_flags, bundle_id)
462 | kill_notification_center()
463 |
464 |
465 | def set_show_in_notification_center(value, bundle_ids):
466 | '''Set the "Show in Notification Center" options'''
467 |
468 | # verify this is a supported value
469 | verify_value_in_allowed(
470 | 'Show in notification center value', value, ['0', '1', '5', '10', '20'])
471 | # exit if no bundle_ids were provided
472 | bundle_ids_or_error_and_exit(bundle_ids)
473 | for bundle_id in bundle_ids:
474 | if not bundleid_exists(bundle_id):
475 | print >> sys.stderr, (
476 | "WARNING: %s not in Notification Center" % bundle_id)
477 | else:
478 | current_flags = get_flags(bundle_id)
479 | if value == '0':
480 | new_flags = current_flags | DONT_SHOW_IN_CENTER
481 | else:
482 | conn, curs = connect_to_db()
483 | curs.execute(
484 | "UPDATE app_info SET show_count='%s' where bundleid='%s'"
485 | % (value, bundle_id))
486 | commit_changes(conn)
487 | new_flags = current_flags & ~DONT_SHOW_IN_CENTER
488 | if new_flags != current_flags:
489 | set_flags(new_flags, bundle_id)
490 |
491 | kill_notification_center()
492 |
493 |
494 | def main():
495 | '''Define and parse options, call our worker functions'''
496 | parser = argparse.ArgumentParser(usage=usage())
497 | parser.add_argument('--verbose', '-v', action='count', default=0,
498 | help='More verbose output from this tool.')
499 | parser.add_argument('--list', '-l', action='store_true',
500 | help='List BUNDLE_IDs in Notification Center database.')
501 |
502 | add_group = parser.add_argument_group(
503 | 'Options for adding or removing apps from Notification Center')
504 | add_group.add_argument('--insert', '-i', metavar='BUNDLE_ID', nargs='+',
505 | help='Add BUNDLE_IDs to Notification Center.')
506 | add_group.add_argument('--remove', '-r', metavar='BUNDLE_ID', nargs='+',
507 | help='Remove BUNDLE_IDs from Notification Center.')
508 | add_group.add_argument('--remove-system-center', action='store_true',
509 | help='Set notification style to \'none\' for all '
510 | '_SYSTEM_CENTER_ items.')
511 |
512 | info_group = parser.add_argument_group('Options for getting information')
513 | info_group.add_argument('--get-alert-style', '-g', metavar='BUNDLE_ID',
514 | help=
515 | 'Get current notification style for BUNDLE_ID.')
516 | info_group.add_argument('--get-show-on-lock-screen',
517 | metavar='BUNDLE_ID',
518 | help='Print state of \'Show notifications on lock '
519 | 'screen\'.')
520 | info_group.add_argument('--get-badge-app-icon', metavar='BUNDLE_ID',
521 | help='Print state of \'Badge app icon\'.')
522 | info_group.add_argument('--get-sound', metavar='BUNDLE_ID',
523 | help=
524 | 'Print state of \'Play sound for notifications\'.')
525 | info_group.add_argument('--get-show-in-notification-center',
526 | metavar='BUNDLE_ID',
527 | help=
528 | 'Print state of \'Show in Notification Center\'.')
529 | info_group.add_argument('--get-info', metavar='BUNDLE_ID',
530 | help='Print current Notification Center settings '
531 | 'for BUNDLE_ID.')
532 |
533 | edit_group = parser.add_argument_group('Options for changing settings')
534 | edit_group.add_argument('--alert-style', '-a',
535 | metavar=('ALERT_STYLE BUNDLE_ID', 'BUNDLE_ID'),
536 | nargs='+',
537 | help='Set notification style for BUNDLE_ID(s). '
538 | 'Supported styles are none, banners, and alerts.')
539 | edit_group.add_argument('--show-on-lock-screen',
540 | metavar=('true|false BUNDLE_ID', 'BUNDLE_ID'),
541 | nargs='+',
542 | help='Set display notifications for BUNDLE_ID(s) '
543 | 'when the screen is locked.')
544 | edit_group.add_argument('--badge-app-icon',
545 | metavar=('true|false BUNDLE_ID', 'BUNDLE_ID'),
546 | nargs='+',
547 | help='Set badge app icon value for BUNDLE_ID(s).')
548 | edit_group.add_argument('--sound',
549 | metavar=('true|false BUNDLE_ID', 'BUNDLE_ID'),
550 | nargs='+',
551 | help=
552 | 'Set notification sound value for BUNDLE_ID(s).')
553 | edit_group.add_argument('--show-in-notification-center',
554 | metavar=('0|1|5|10|20 BUNDLE_ID', 'BUNDLE_ID'),
555 | nargs='+',
556 | help='Set "Show in Notification Center" options '
557 | 'for BUNDLE_ID(s).')
558 |
559 | options = parser.parse_args()
560 |
561 | # make sure at least one option has been chosem
562 | one_attr_set = False
563 | options_dict = vars(options)
564 | for key in options_dict.keys():
565 | if options_dict[key]:
566 | one_attr_set = True
567 | break
568 | if not one_attr_set:
569 | parser.print_help()
570 | exit(1)
571 |
572 | # process options
573 | if options.list:
574 | list_clients()
575 | if options.insert:
576 | insert_app(options.insert)
577 | if options.remove:
578 | remove_app(options.remove)
579 | if options.remove_system_center:
580 | remove_system_center()
581 | if options.get_alert_style:
582 | get_alert_style(options.get_alert_style)
583 | if options.get_info:
584 | get_info(options.get_info)
585 | if options.get_show_on_lock_screen:
586 | get_show_on_lock_screen(options.get_show_on_lock_screen)
587 | if options.get_badge_app_icon:
588 | get_badge_app_icon(options.get_badge_app_icon)
589 | if options.get_sound:
590 | get_notification_sound(options.get_sound)
591 | if options.get_show_in_notification_center:
592 | get_show_in_notification_center(options.get_show_in_notification_center)
593 | if options.alert_style:
594 | set_alert(options.alert_style[0], options.alert_style[1:])
595 | if options.show_on_lock_screen:
596 | set_show_on_lock_screen(
597 | options.show_on_lock_screen[0], options.show_on_lock_screen[1:])
598 | if options.badge_app_icon:
599 | set_badge_app_icon(
600 | options.badge_app_icon[0], options.badge_app_icon[1:])
601 | if options.sound:
602 | set_notification_sound(options.sound[0], options.sound[1:])
603 | if options.show_in_notification_center:
604 | set_show_in_notification_center(options.show_in_notification_center[0],
605 | options.show_in_notification_center[1:])
606 | if __name__ == "__main__":
607 | main()
608 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | NCutil
2 | ======
3 |
4 | Notification Center command line utility - ***Add and remove apps, set alert styles.***
5 | OS Mavericks and [Yosemite](https://github.com/jasonpjohnson/NCutil/commit/3028e8baccc646b60712fa0cc08de2be52b4e11b). View changes via the GUI in **real-time**.
6 |
7 | # Short Demo Video
8 |
11 |
12 |
13 | # Add and remove apps from Notification Center
14 | Adding and removing apps is perfect for deploying software silently or having it pre-configured so the user doesn't have to do anything.
15 |
16 | ## Add apps
17 |
18 | - ```NCutil.py -i com.noodlesoft.HazelHelper```
19 | - ```NCutil.py --insert com.noodlesoft.HazelHelper```
20 |
21 | ## Remove apps
22 |
23 | - ```NCutil.py -r com.barebones.textwrangler```
24 | - ```NCutil.py --remove com.barebones.textwrangler```
25 |
26 | # Get Current Settings
27 | Running a command like this:
28 |
29 | - ```NCutil.py --get-info com.apple.reminders```
30 |
31 | will return something like this:
32 |
33 | ```
34 | Notification Center settings for Reminders.app:
35 | Reminders.app alert style: Alerts
36 | Show notifications on lock screen: Yes
37 | Show message preview: Always
38 | Show in Notification Center: 5 Recent Items
39 | Badge app icon: Yes
40 | Play sound for notifications: Yes
41 | ```
42 |
43 | ## Get Individual Settings
44 |
45 | ### Get The Current Alert Settings
46 |
47 | You can find out what the app's current alert setting is with the `-g` flag or `--get-alert-style`.
48 |
49 | - ```NCutil.py -g com.teamviewer.TeamViewer```
50 | - ```NCutil.py --get-alert-style com.teamviewer.TeamViewer```
51 |
52 | which will return a one line response: `none`, `banners`, or `alerts`
53 |
54 | ### Get Other Settings
55 |
56 | You can check if individual settings are on or off using some of the examples shown below, which will return a one line response.
57 |
58 | `NCutil.py --get-show-on-lock-screen com.apple.iCal`
59 |
60 | returns `true` or `false`
61 |
62 | `NCutil.py --get-badge-app-icon com.apple.iCal`
63 |
64 | returns `true` or `false`
65 |
66 | `NCutil.py --get-sound com.apple.iCal`
67 |
68 | returns `true` or `false`
69 |
70 | `NCutil.py --get-show-in-notification-center com.apple.iCal`
71 |
72 | returns a number: `0`, `5`, `10`, or `20`
73 |
74 | # Change Settings
75 |
76 | ## Adjust Alert Duration (Alerts, Banners, or None)
77 |
78 | - ```NCutil.py -a alerts com.apple.Safari```
79 | - ```NCutil.py -a banners com.apple.reminders```
80 | - ```NCutil.py --alert-style none com.apple.appstore```
81 |
82 | ## Adjust Other Checkbox Settings
83 |
84 | You can adjust any of the checkboxes in the GUI such as the badge icon, number of recent items, whether or not to show it on the lock screen, etc.
85 | 
86 |
87 | Don't show iCal Notifications on the lock screen
88 |
89 | - ```NCutil.py --show-on-lock-screen true com.apple.iCal```
90 |
91 | Disable the badge app icon for Message
92 |
93 | - ```NCutil.py --badge-app-icon false com.apple.iChat```
94 |
95 | Disable the sound for TextWrangler
96 |
97 | - ```NCutil.py --sound false com.barebones.textwrangler```
98 |
99 | Set the amount of recent Notifications to show to 20 for Dropbox
100 |
101 | - ```NCutil.py --show-in-notification-center 20 com.getdropbox.dropbox```
102 |
103 | # Multiple Bundle IDs
104 |
105 | Most of the options like `--insert`, `--remove`, or `--alert-style`, allow you to add multiple bundle IDs to modify the same setting for multiple apps.
106 |
107 | - ```NCutil.py --remove com.noodlesoft.HazelHelper com.apple.Safari com.apple.reminders```
108 |
109 | # `_SYSTEM_CENTER_` Notifications
110 |
111 | Apple has a lot of different apps that show notifications, which do not show up in the GUI. You can remove **all** of these hidden Notification sources by using the `-remove-system-center` option but is not fully-supported as we don't know what they all do. If you decide to try it, this is the equivalent to setting each one individually to an alert style of `none`.
112 |
113 | - ```NCutil.py --remove-system-center```
114 |
115 | ## Remove `_SYSTEM_CENTER_` At Your Own Risk
116 |
117 | To add a little more detail to the command above, the `_SYSTEM_CENTER_` entries are hidden from the GUI. Apple is obviously not expecting users to change any of those preferences (since there is no UI to do so) and so it would be prudent to not modify those. However, this utility lets you do that. You can do so *at your own risk*. Personally, I have had them turned off for a few weeks now without issue, but that doesn't mean it won't break later.
118 |
119 | Additionally, if there were some sources you still wanted to have notification for, you can simply re-enable them on an individual basis.
120 |
121 | - ```NCutil.py -a banners _SYSTEM_CENTER_:com.apple.storeagent```
122 | - ```NCutil.py -a banners _SYSTEM_CENTER_:com.apple.battery-monitor```
123 |
124 | ### Suppress Apple Update Notifications Like The "Free Yosemite Upgrade"
125 | From what I can tell, these are the items you need to disable to [stop the Yosemite upgrade Notification](http://jacobsalmela.com/hide-free-yosemite-upgrade-notification-with-ncutil-py/).
126 |
127 | 
128 |
129 | - ```NCutil.py -a none _SYSTEM_CENTER_:com.apple.storeagent```
130 | - ```NCutil.py -a none _SYSTEM_CENTER_:com.apple.noticeboard```
131 |
132 | Disabling the App Store Notifications may also help:
133 |
134 | - ```NCutil.py --alerts none com.apple.maspushagent```
135 |
136 | # Known Issues
137 |
138 | If Do Not Disturb is **on** and you run a command that modifies a setting, Do Not Disturb will be **turned off** unintentionally. This seems to **only happen in Mavericks** and is likely caused by the `killall NotificiationCenter`, which is what allows the commands to show up in real time.
139 |
140 | 
141 |
142 | # Changelog
143 |
144 | **2.4**
145 |
146 | - added support for OS X (10.11) El Capitan
147 |
148 | **2.3**
149 |
150 | - added `--get-show-on-lock-screen`
151 | - added `--get-badge-app-icon`
152 | - added `--get-sound`
153 | - added `--get-show-in-notification-center`
154 | - reformatted help menu into groups of similar settings
155 |
156 | **2.2**
157 |
158 | - `--get-info` allows you to see what all the current settings are
159 | - `--show-on-lock-screen` can now be set to `true` or `false`
160 | - `--badge-app-icon` can now be set to `true` or `false`
161 | - `--sound` can now be set to `true` or `false`
162 | - `--show-in-notification-center` can now be set to `0`, `5`, `10` or `20`
163 | - improved help menu
164 | - reduced verbosity of `--get-alert-style` since more information can be found with `--get-info`
165 |
166 |
167 | **2.1**
168 |
169 | - `--get-alert-style` allows you to see what alert style the app currently is set to
170 | - `--remove-system-center` removes all hidden notification, but do so at your own risk
171 | - allow multiple arguments for `--insert`, `--remove`, and `--alert-style`
172 | - syntax changed to `--alert-style` from `--alertstyle` for easier readability
173 | - find the most recently used .db if multiple ones exist
174 |
175 | **2.0**
176 |
177 | - Yosemite Support
178 |
179 | **1.0**
180 |
181 | - Initial release. Mavericks support.
182 |
--------------------------------------------------------------------------------