def _notification_check(self, summary, body): (cb, mtime), ts = self._filter_callback, time() if self._filter_ts_chk < ts - poll_interval: self._filter_ts_chk = ts try: ts = int(os.stat(optz.filter_file).st_mtime) except (OSError, IOError): return True if ts > mtime: mtime = ts try: cb = core.get_filter(optz.filter_file, optz.filter_sound) except: ex, self._filter_callback = traceback.format_exc(), (None, 0) log.debug( 'Failed to load' ' notification filters (from %s):\n%s', optz.filter_file, ex ) if optz.status_notify: self.display('notification-thing: failed to load notification filters', ex) return True else: log.debug('(Re)Loaded notification filters') self._filter_callback = cb, mtime if cb is None: return True # no filtering defined elif not callable(cb): return bool(cb) try: return cb(summary, body) except: ex = traceback.format_exc() log.debug('Failed to execute notification filters:\n%s', ex) if optz.status_notify: self.display('notification-thing: notification filters failed', ex) return True
def main(argv=None): global optz, log import argparse parser = argparse.ArgumentParser(description='Desktop notification server.') parser.add_argument('--conf', metavar='path', help='Read option values from specified YAML configuration file.' ' Keys in subsectons (like "tbf.size") will be joined with' ' parent section name with dash (e.g. --tbf-size).' ' Any values specified on command line will override corresponding ones from file.' ' See also notification_thing.example.yaml file.') parser.add_argument('--conf-missing-ok', action='store_true', help='Safely ignore missing or inaccessible configuration files.') parser.add_argument('--debug', action='store_true', help='Enable debug logging to stderr.') group = parser.add_argument_group('Daemon operation settings and notification defaults') group.add_argument('-c', '--activity-timeout', type=float, default=float(optz['activity_timeout']), metavar='seconds', help='No-activity (dbus calls) timeout before closing the daemon instance' ' (less or equal zero - infinite, default: %(default)ss)') group.add_argument('-t', '--popup-timeout', type=float, default=float(optz['popup_timeout']*1000), metavar='seconds', help='Default timeout for notification popups removal (default: %(default)sms)') group.add_argument('-q', '--queue-len', type=int, default=optz['queue_len'], metavar='n', help='How many messages should be queued on tbf overflow (default: %(default)s)') group.add_argument('-s', '--history-len', type=int, default=optz['history_len'], metavar='n', help='How many last *displayed* messages to' ' remember to display again on demand (default: %(default)s)') group = parser.add_argument_group('Test/startup messages and sounds') group.add_argument('--test-message', action='store_true', help='Issue test notification right after start.') group.add_argument('--test-sound', metavar='path', help='Play specified sound on startup,' ' if sound support is available and enabled for filtering rules.') group = parser.add_argument_group('Basic notification processing options') group.add_argument('-f', '--no-fs-check', action='store_false', dest='fs_check', default=True, help='Dont queue messages if active window is fullscreen') group.add_argument('-u', '--no-urgency-check', action='store_false', dest='urgency_check', default=True, help='Queue messages even if urgency is critical') group.add_argument('--no-status-notify', action='store_false', dest='status_notify', default=True, help='Do not send notification on changes in daemon settings.') group = parser.add_argument_group('Scheme-based notification filtering') group.add_argument('--filter-file', default='~/.notification_filter', metavar='path', help='Read simple scheme rules for filtering notifications from file (default: %(default)s).') group.add_argument('--filter-test', nargs=2, metavar=('summary', 'body'), help='Do not start daemon, just test given summary' ' and body against filter-file and print the result back to terminal.') group.add_argument('--no-filter-sound', action='store_false', dest='filter_sound', default=True, help='Make sound calls in --filters-file scheme interpreter a no-op.' ' Only makes sense if sound calls in filters are actually used' ' and libcanberra is available, otherwise there wont be any sounds anyway.') group = parser.add_argument_group('Display layout (e.g. position, direction, etc) options') group.add_argument('--layout-anchor', choices=core.layout_anchor, default='top_left', help='Screen corner notifications gravitate to (default: %(default)s).') group.add_argument('--layout-direction', choices=core.layout_direction, default='vertical', help='Direction for notification stack growth' ' from --layout-anchor corner (default: %(default)s).') group.add_argument('--layout-margin', default=3, type=int, metavar='px', help='Margin between notifications, screen edges, and some misc stuff (default: %(default)spx).') group = parser.add_argument_group('Icon scaling options') group.add_argument('--icon-width', '--img-w', type=int, metavar='px', help='Scale all icons (preserving aspect ratio) to specified width.' ' In --icon-height is also specified, icons will be "fitted" into a WxH box, never larger.') group.add_argument('--icon-height', '--img-h', type=int, metavar='px', help='Scale all icons (preserving aspect ratio) to specified height.' ' In --icon-width is also specified, icons will be "fitted" into a WxH box, never larger.') group.add_argument('--icon-size-max', '--img-max', metavar='WxH', help='Scale icons larger than specified size' ' in any dimension down to it, preserving aspect ratio.' ' Value must be in "[w]x[h]" format, with w/h' ' being integers for size in pixels, either one or both of them can be specified.' ' Differs from --icon-width/--icon-height in' ' that it only scales-down larger ones, not all icons.') group.add_argument('--icon-size-min', '--img-min', metavar='WxH', help='Same as --icon-size-max, but scales-up smaller images to a specified min size.') group = parser.add_argument_group('Text pango markup options') group.add_argument('--markup-disable', action='store_true', help='Enable pango markup (tags, somewhat similar to html)' ' processing in all message summary/body parts by default.' ' These will either be rendered by pango' ' or stripped/shown-as-is (see --pango-markup-strip-on-err option), if invalid.' ' "x-nt-markup" bool hint can be used to' ' enable/disable markup on per-message basis, regardless of this option value.' ' See "Markup" section of the README for more details.') group.add_argument('--markup-strip-on-err', action='store_true', help='Strip markup tags if pango fails to parse them' ' (when parsing markup is enabled) instead of rendering message with them as text.') group.add_argument('--markup-warn-on-err', action='store_true', help='Issue loggin warning if passed markup tags cannot be parsed (when it is enabled).') group = parser.add_argument_group('Token-bucket message rate-limiting options') group.add_argument('--tbf-size', type=int, default=optz['tbf_size'], metavar='n', help='Token-bucket message-flow filter (tbf) bucket size (default: %(default)s)') group.add_argument('--tbf-tick', type=float, default=optz['tbf_tick'], metavar='seconds', help='tbf update interval (new token),' ' so token_inflow = token / tbf_tick (default: %(default)ss)') group.add_argument('--tbf-max-delay', type=float, default=optz['tbf_max_delay'], metavar='seconds', help='Maxmum amount of seconds, between message queue flush (default: %(default)ss)') group.add_argument('--tbf-inc', type=float, default=optz['tbf_inc'], metavar='value', help='tbf_tick multiplier on consequent tbf overflow (default: %(default)s)') group.add_argument('--tbf-dec', type=float, default=optz['tbf_dec'], metavar='value', help='tbf_tick divider on successful grab from non-empty bucket,' ' wont lower multiplier below 1 (default: %(default)s)') group.add_argument('--feed-icon', type=float, default=optz['feed_icon'], metavar='icon', help='Icon name/path to use for aggregated ("feed") notification.' ' Can be full path to icon, local "file://..." url,' ' or name from xdg theme (e.g. "face-smile").') group = parser.add_argument_group('DBus options') group.add_argument('--dbus-interface', default=optz['dbus_interface'], metavar='iface', help='DBus interface to use (default: %(default)s)') group.add_argument('--dbus-path', default=optz['dbus_path'], metavar='path', help='DBus object path to bind to (default: %(default)s)') group = parser.add_argument_group('Network pub/sub options') group.add_argument('--net-pub-bind', action='append', metavar='ip:port', help='Publish messages over network on a specified socket endpoint.' ' Can be either ip:port (assumed to be tcp socket,' ' e.g. 1.2.3.4:5678) or full zmq url (e.g. tcp://1.2.3.4:5678).' ' Can be specified multiple times.') group.add_argument('--net-pub-connect', action='append', metavar='ip:port', help='Send published messages to specified subscriber socket (see --net-sub-bind).' ' Same format as for --net-pub-bind. Can be specified multiple times.') group.add_argument('--net-sub-bind', action='append', metavar='ip:port', help='Create subscriber socket that' ' publishers can connect and send messages to (see --net-pub-connect).' ' Same format as for --net-pub-bind. Can be specified multiple times.') group.add_argument('--net-sub-connect', action='append', metavar='ip:port', help='Receive published messages from a specified pub socket (see --net-pub-bind).' ' Same format as for --net-pub-bind. Can be specified multiple times.') group.add_argument('--net-settings', metavar='yaml', help='Optional yaml/json encoded settings' ' for PubSub class init (e.g. hostname, buffer, reconnect_max, etc).') group = parser.add_argument_group('Message logging options') group.add_argument('--log-file', metavar='file', help='Path to a log file to record all messages to.' ' Initial ~ or ~user components are auto-expanded to home dirs.') group.add_argument('--log-filtered', action='store_true', help='Enable logging for messages that did not pass through filtering checks.') group.add_argument('--log-rotate-files', type=int, metavar='file-count', help='Enable automatic log file roatation with specified number of old files kept.') group.add_argument('--log-rotate-size', type=int, metavar='bytes', default='1048576', help='Rotate files after they get larger' ' than specified number of bytes. Default: 1 MiB.') args = argv or sys.argv[1:] optz = parser.parse_args(args) if optz.conf_missing_ok and not os.access(optz.conf, os.R_OK): optz.conf = None if optz.conf: import yaml for k, v in flatten_dict(yaml.safe_load(open(optz.conf)) or dict()): if v is None: continue k = '_'.join(k).replace('-', '_') if not hasattr(optz, k): parser.error('Unrecognized option in config file ({}): {}'.format(optz.conf, k)) setattr(optz, k, v) optz = parser.parse_args(args, optz) # re-parse to override cli-specified values for k in 'layout_anchor', 'layout_direction': setattr(optz, k, getattr(core, k)[getattr(optz, k)]) import logging logging.basicConfig(level=logging.DEBUG if optz.debug else logging.WARNING) log = logging.getLogger('daemon') optz.filter_file = os.path.expanduser(optz.filter_file) core.Notification.default_timeout = optz.popup_timeout if optz.filter_sound: optz.filter_sound = core.get_sound_env( force_sync=optz.filter_test, trap_errors=not (optz.filter_test or optz.debug) ) if optz.filter_test: func = core.get_filter(optz.filter_file, optz.filter_sound) filtering_result = func(*optz.filter_test) msg_repr = 'Message - summary: {!r}, body: {!r}'.format(*optz.filter_test) print('{}\nFiltering result: {} ({})'.format( msg_repr, filtering_result, 'will pass' if filtering_result else "won't pass" )) return optz.icon_scale = dict() if optz.icon_width or optz.icon_height: optz.icon_scale['fixed'] = optz.icon_width, optz.icon_height else: for k in 'min', 'max': v = getattr(optz, 'icon_size_{}'.format(k)) if not v: continue try: if 'x' not in v: raise ValueError if v.endswith('x'): v += '0' if v.startswith('x'): v = '0' + v w, h = map(int, v.split('x')) except: parser.error( 'Invalid --icon-size-{}' ' value (must be in "[w]x[h]" format): {!r}'.format(k, v) ) optz.icon_scale[k] = w, h DBusGMainLoop(set_as_default=True) bus = dbus.SessionBus() pubsub = None if optz.net_pub_bind or optz.net_pub_connect\ or optz.net_sub_bind or optz.net_sub_connect: if optz.net_settings and not isinstance(optz.net_settings, Mapping): import yaml optz.net_settings = yaml.safe_load(optz.net_settings) pubsub = PubSub(**(optz.net_settings or dict())) for addrs, call in [ (optz.net_pub_bind, pubsub.bind_pub), (optz.net_sub_bind, pubsub.bind_sub), (optz.net_pub_connect, pubsub.connect), (optz.net_sub_connect, pubsub.subscribe) ]: if isinstance(addrs, types.StringTypes): addrs = [addrs] for addr in set(addrs or set()): log.debug('zmq link: %s %s', call.im_func.func_name, addr) call(addr) logger = None if optz.log_file: logger = FileLogger( os.path.expanduser(optz.log_file), files=optz.log_rotate_files, size_limit=optz.log_rotate_size ) daemon = notification_daemon_factory( pubsub, logger, bus, optz.dbus_path, dbus.service.BusName(optz.dbus_interface, bus) ) loop = GLib.MainLoop() log.debug('Starting gobject loop') loop.run()
def main(): global optz, log import argparse def EnumAction(enum): class EnumAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, self.enum[values]) EnumAction.enum = enum return EnumAction parser = argparse.ArgumentParser( description='Desktop notification server.') parser.add_argument( '-f', '--no-fs-check', action='store_false', dest='fs_check', default=True, help='Dont queue messages if active window is fullscreen') parser.add_argument('-u', '--no-urgency-check', action='store_false', dest='urgency_check', default=True, help='Queue messages even if urgency is critical') parser.add_argument( '-c', '--activity-timeout', type=int, default=int(optz['activity_timeout']), help= 'No-activity (dbus calls) timeout before closing the daemon instance' ' (less or equal zero - infinite, default: %(default)ss)') parser.add_argument( '--no-status-notify', action='store_false', dest='status_notify', default=True, help='Do not send notification on changes in proxy settings.') parser.add_argument( '--filter-file', default='~/.notification_filter', metavar='PATH', help= 'Read simple scheme rules for filtering notifications from file (default: %(default)s).' ) parser.add_argument( '--filter-test', nargs=2, metavar=('SUMMARY', 'BODY'), help='Do not start daemon, just test given summary' ' and body against filter-file and print the result back to terminal.') parser.add_argument( '-t', '--popup-timeout', type=int, default=int(optz['popup_timeout'] * 1000), help= 'Default timeout for notification popups removal (default: %(default)sms)' ) parser.add_argument( '-q', '--queue-len', type=int, default=optz['queue_len'], help= 'How many messages should be queued on tbf overflow (default: %(default)s)' ) parser.add_argument( '--layout-anchor', choices=core.layout_anchor, action=EnumAction(core.layout_anchor), default=core.layout_anchor.top_left, help='Screen corner notifications gravitate to (default: top_left).') parser.add_argument( '--layout-direction', choices=core.layout_direction, action=EnumAction(core.layout_direction), default=core.layout_direction.vertical, help= 'Direction for notification stack growth from --layout-anchor corner (default: vertical).' ) parser.add_argument( '--layout-margin', default=3, help= 'Margin between notifications, screen edges, and some misc stuff (default: %(default)spx).' ) parser.add_argument( '--tbf-size', type=int, default=optz['tbf_size'], help= 'Token-bucket message-flow filter (tbf) bucket size (default: %(default)s)' ) parser.add_argument( '--tbf-tick', type=int, default=optz['tbf_tick'], help= 'tbf update interval (new token), so token_inflow = token / tbf_tick (default: %(default)ss)' ) parser.add_argument( '--tbf-max-delay', type=int, default=optz['tbf_max_delay'], help= 'Maxmum amount of seconds, between message queue flush (default: %(default)ss)' ) parser.add_argument( '--tbf-inc', type=int, default=optz['tbf_inc'], help= 'tbf_tick multiplier on consequent tbf overflow (default: %(default)s)' ) parser.add_argument( '--tbf-dec', type=int, default=optz['tbf_dec'], help='tbf_tick divider on successful grab from non-empty bucket,' ' wont lower multiplier below 1 (default: %(default)s)') parser.add_argument('--img-w', type=int, default=optz["img_w"], help='max image icon width') parser.add_argument('--img-h', type=int, default=optz["img_h"], help='max image icon height') parser.add_argument('--debug', action='store_true', help='Enable debug logging to stderr.') optz = parser.parse_args() optz.filter_file = os.path.expanduser(optz.filter_file) core.Notification.default_timeout = optz.popup_timeout if optz.filter_test: func = core.get_filter(optz.filter_file) filtering_result = func(*optz.filter_test) msg_repr = 'Message - summary: {!r}, body: {!r}'.format( *optz.filter_test) print('{}\nFiltering result: {} ({})'.format( msg_repr, filtering_result, 'will pass' if filtering_result else "won't pass")) sys.exit() import logging logging.basicConfig(level=logging.DEBUG if optz.debug else logging.WARNING) log = logging.getLogger() daemon = NotificationDaemon(bus, core.dbus_path, dbus.service.BusName(core.dbus_id, bus)) loop = GObject.MainLoop() log.debug('Starting gobject loop') loop.run()
def main(argv=None): global optz, log import argparse parser = argparse.ArgumentParser(description="Desktop notification server.") parser.add_argument( "--conf", metavar="path", help="Read option values from specified YAML configuration file." ' Keys in subsectons (like "tbf.size") will be joined with' " parent section name with dash (e.g. --tbf-size)." " Any values specified on command line will override corresponding ones from file." " See also notification_thing.example.yaml file.", ) parser.add_argument( "--conf-missing-ok", action="store_true", help="Safely ignore missing or inaccessible configuration files." ) parser.add_argument("--debug", action="store_true", help="Enable debug logging to stderr.") group = parser.add_argument_group("Daemon operation settings and notification defaults") group.add_argument( "-c", "--activity-timeout", type=float, default=float(optz["activity_timeout"]), metavar="seconds", help="No-activity (dbus calls) timeout before closing the daemon instance" " (less or equal zero - infinite, default: %(default)ss)", ) group.add_argument( "-t", "--popup-timeout", type=float, default=float(optz["popup_timeout"] * 1000), metavar="seconds", help="Default timeout for notification popups removal (default: %(default)sms)", ) group.add_argument( "-q", "--queue-len", type=int, default=optz["queue_len"], metavar="n", help="How many messages should be queued on tbf overflow (default: %(default)s)", ) group.add_argument( "-s", "--history-len", type=int, default=optz["history_len"], metavar="n", help="How many last *displayed* messages to" " remember to display again on demand (default: %(default)s)", ) group = parser.add_argument_group("Test/startup messages and sounds") group.add_argument("--test-message", action="store_true", help="Issue test notification right after start.") group.add_argument( "--test-sound", metavar="path", help="Play specified sound on startup," " if sound support is available and enabled for filtering rules.", ) group = parser.add_argument_group("Basic notification processing options") group.add_argument( "-f", "--no-fs-check", action="store_false", dest="fs_check", default=True, help="Dont queue messages if active window is fullscreen", ) group.add_argument( "-u", "--no-urgency-check", action="store_false", dest="urgency_check", default=True, help="Queue messages even if urgency is critical", ) group.add_argument( "--no-status-notify", action="store_false", dest="status_notify", default=True, help="Do not send notification on changes in daemon settings.", ) group = parser.add_argument_group("Scheme-based notification filtering") group.add_argument( "--filter-file", default="~/.notification_filter", metavar="path", help="Read simple scheme rules for filtering notifications from file (default: %(default)s).", ) group.add_argument( "--filter-test", nargs=2, metavar=("summary", "body"), help="Do not start daemon, just test given summary" " and body against filter-file and print the result back to terminal.", ) group.add_argument( "--no-filter-sound", action="store_false", dest="filter_sound", default=True, help="Make sound calls in --filters-file scheme interpreter a no-op." " Only makes sense if sound calls in filters are actually used" " and libcanberra is available, otherwise there wont be any sounds anyway.", ) group = parser.add_argument_group("Display layout (e.g. position, direction, etc) options") group.add_argument( "--layout-anchor", choices=core.layout_anchor, default="top_left", help="Screen corner notifications gravitate to (default: %(default)s).", ) group.add_argument( "--layout-direction", choices=core.layout_direction, default="vertical", help="Direction for notification stack growth" " from --layout-anchor corner (default: %(default)s).", ) group.add_argument( "--layout-margin", default=3, type=int, metavar="px", help="Margin between notifications, screen edges, and some misc stuff (default: %(default)spx).", ) group = parser.add_argument_group("Icon scaling options") group.add_argument( "--icon-width", "--img-w", type=int, metavar="px", help="Scale all icons (preserving aspect ratio) to specified width." ' In --icon-height is also specified, icons will be "fitted" into a WxH box, never larger.', ) group.add_argument( "--icon-height", "--img-h", type=int, metavar="px", help="Scale all icons (preserving aspect ratio) to specified height." ' In --icon-width is also specified, icons will be "fitted" into a WxH box, never larger.', ) group.add_argument( "--icon-size-max", "--img-max", metavar="WxH", help="Scale icons larger than specified size" " in any dimension down to it, preserving aspect ratio." ' Value must be in "[w]x[h]" format, with w/h' " being integers for size in pixels, either one or both of them can be specified." " Differs from --icon-width/--icon-height in" " that it only scales-down larger ones, not all icons.", ) group.add_argument( "--icon-size-min", "--img-min", metavar="WxH", help="Same as --icon-size-max, but scales-up smaller images to a specified min size.", ) group = parser.add_argument_group("Text pango markup options") group.add_argument( "--markup-disable", action="store_true", help="Enable pango markup (tags, somewhat similar to html)" " processing in all message summary/body parts by default." " These will either be rendered by pango" " or stripped/shown-as-is (see --pango-markup-strip-on-err option), if invalid." ' "x-nt-markup" bool hint can be used to' " enable/disable markup on per-message basis, regardless of this option value." ' See "Markup" section of the README for more details.', ) group.add_argument( "--markup-strip-on-err", action="store_true", help="Strip markup tags if pango fails to parse them" " (when parsing markup is enabled) instead of rendering message with them as text.", ) group.add_argument( "--markup-warn-on-err", action="store_true", help="Issue loggin warning if passed markup tags cannot be parsed (when it is enabled).", ) group = parser.add_argument_group("Token-bucket message rate-limiting options") group.add_argument( "--tbf-size", type=int, default=optz["tbf_size"], metavar="n", help="Token-bucket message-flow filter (tbf) bucket size (default: %(default)s)", ) group.add_argument( "--tbf-tick", type=float, default=optz["tbf_tick"], metavar="seconds", help="tbf update interval (new token), so token_inflow = token / tbf_tick (default: %(default)ss)", ) group.add_argument( "--tbf-max-delay", type=float, default=optz["tbf_max_delay"], metavar="seconds", help="Maxmum amount of seconds, between message queue flush (default: %(default)ss)", ) group.add_argument( "--tbf-inc", type=float, default=optz["tbf_inc"], metavar="value", help="tbf_tick multiplier on consequent tbf overflow (default: %(default)s)", ) group.add_argument( "--tbf-dec", type=float, default=optz["tbf_dec"], metavar="value", help="tbf_tick divider on successful grab from non-empty bucket," " wont lower multiplier below 1 (default: %(default)s)", ) group = parser.add_argument_group("DBus options") group.add_argument( "--dbus-interface", default=optz["dbus_interface"], metavar="iface", help="DBus interface to use (default: %(default)s)", ) group.add_argument( "--dbus-path", default=optz["dbus_path"], metavar="path", help="DBus object path to bind to (default: %(default)s)", ) group = parser.add_argument_group("Network pub/sub options") group.add_argument( "--net-pub-bind", action="append", metavar="ip:port", help="Publish messages over network on a specified socket endpoint." " Can be either ip:port (assumed to be tcp socket," " e.g. 1.2.3.4:5678) or full zmq url (e.g. tcp://1.2.3.4:5678)." " Can be specified multiple times.", ) group.add_argument( "--net-pub-connect", action="append", metavar="ip:port", help="Send published messages to specified subscriber socket (see --net-sub-bind)." " Same format as for --net-pub-bind. Can be specified multiple times.", ) group.add_argument( "--net-sub-bind", action="append", metavar="ip:port", help="Create subscriber socket that" " publishers can connect and send messages to (see --net-pub-connect)." " Same format as for --net-pub-bind. Can be specified multiple times.", ) group.add_argument( "--net-sub-connect", action="append", metavar="ip:port", help="Receive published messages from a specified pub socket (see --net-pub-bind)." " Same format as for --net-pub-bind. Can be specified multiple times.", ) group.add_argument( "--net-settings", metavar="yaml", help="Optional yaml/json encoded settings" " for PubSub class init (e.g. hostname, buffer, reconnect_max, etc).", ) args = argv or sys.argv[1:] optz = parser.parse_args(args) if optz.conf_missing_ok and not os.access(optz.conf, os.R_OK): optz.conf = None if optz.conf: import yaml for k, v in flatten_dict(yaml.safe_load(open(optz.conf)) or dict()): if v is None: continue k = "_".join(k).replace("-", "_") if not hasattr(optz, k): parser.error("Unrecognized option in config file ({}): {}".format(optz.conf, k)) setattr(optz, k, v) optz = parser.parse_args(args, optz) # re-parse to override cli-specified values for k in "layout_anchor", "layout_direction": setattr(optz, k, getattr(core, k)[getattr(optz, k)]) import logging logging.basicConfig(level=logging.DEBUG if optz.debug else logging.WARNING) log = logging.getLogger("daemon") optz.filter_file = os.path.expanduser(optz.filter_file) core.Notification.default_timeout = optz.popup_timeout if optz.filter_sound: optz.filter_sound = core.get_sound_env( force_sync=optz.filter_test, trap_errors=not (optz.filter_test or optz.debug) ) if optz.filter_test: func = core.get_filter(optz.filter_file, optz.filter_sound) filtering_result = func(*optz.filter_test) msg_repr = "Message - summary: {!r}, body: {!r}".format(*optz.filter_test) print( "{}\nFiltering result: {} ({})".format( msg_repr, filtering_result, "will pass" if filtering_result else "won't pass" ) ) sys.exit() optz.icon_scale = dict() if optz.icon_width or optz.icon_height: optz.icon_scale["fixed"] = optz.icon_width, optz.icon_height else: for k in "min", "max": v = getattr(optz, "icon_size_{}".format(k)) if not v: continue try: if "x" not in v: raise ValueError if v.endswith("x"): v += "0" if v.startswith("x"): v = "0" + v w, h = map(int, v.split("x")) except: parser.error("Invalid --icon-size-{}" ' value (must be in "[w]x[h]" format): {!r}'.format(k, v)) optz.icon_scale[k] = w, h DBusGMainLoop(set_as_default=True) bus = dbus.SessionBus() if optz.net_pub_bind or optz.net_pub_connect or optz.net_sub_bind or optz.net_sub_connect: if optz.net_settings and not isinstance(optz.net_settings, Mapping): import yaml optz.net_settings = yaml.safe_load(optz.net_settings) pubsub = PubSub(**(optz.net_settings or dict())) for addrs, call in [ (optz.net_pub_bind, pubsub.bind_pub), (optz.net_sub_bind, pubsub.bind_sub), (optz.net_pub_connect, pubsub.connect), (optz.net_sub_connect, pubsub.subscribe), ]: if isinstance(addrs, types.StringTypes): addrs = [addrs] for addr in set(addrs or set()): log.debug("zmq link: %s %s", call.im_func.func_name, addr) call(addr) else: pubsub = None daemon = notification_daemon_factory(pubsub, bus, optz.dbus_path, dbus.service.BusName(optz.dbus_interface, bus)) loop = GLib.MainLoop() log.debug("Starting gobject loop") loop.run()
def main(): global optz, log import argparse def EnumAction(enum): class EnumAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, self.enum[values]) EnumAction.enum = enum return EnumAction parser = argparse.ArgumentParser(description='Desktop notification server.') parser.add_argument('-f', '--no-fs-check', action='store_false', dest='fs_check', default=True, help='Dont queue messages if active window is fullscreen') parser.add_argument('-u', '--no-urgency-check', action='store_false', dest='urgency_check', default=True, help='Queue messages even if urgency is critical') parser.add_argument('-c', '--activity-timeout', type=int, default=int(optz['activity_timeout']), help='No-activity (dbus calls) timeout before closing the daemon instance' ' (less or equal zero - infinite, default: %(default)ss)') parser.add_argument('--no-status-notify', action='store_false', dest='status_notify', default=True, help='Do not send notification on changes in proxy settings.') parser.add_argument('--filter-file', default='~/.notification_filter', metavar='PATH', help='Read simple scheme rules for filtering notifications from file (default: %(default)s).') parser.add_argument('--filter-test', nargs=2, metavar=('SUMMARY', 'BODY'), help='Do not start daemon, just test given summary' ' and body against filter-file and print the result back to terminal.') parser.add_argument('-t', '--popup-timeout', type=int, default=int(optz['popup_timeout']*1000), help='Default timeout for notification popups removal (default: %(default)sms)') parser.add_argument('-q', '--queue-len', type=int, default=optz['queue_len'], help='How many messages should be queued on tbf overflow (default: %(default)s)') parser.add_argument('--layout-anchor', choices=core.layout_anchor, action=EnumAction(core.layout_anchor), default=core.layout_anchor.top_left, help='Screen corner notifications gravitate to (default: top_left).') parser.add_argument('--layout-direction', choices=core.layout_direction, action=EnumAction(core.layout_direction), default=core.layout_direction.vertical, help='Direction for notification stack growth from --layout-anchor corner (default: vertical).') parser.add_argument('--layout-margin', default=3, help='Margin between notifications, screen edges, and some misc stuff (default: %(default)spx).') parser.add_argument('--tbf-size', type=int, default=optz['tbf_size'], help='Token-bucket message-flow filter (tbf) bucket size (default: %(default)s)') parser.add_argument('--tbf-tick', type=int, default=optz['tbf_tick'], help='tbf update interval (new token), so token_inflow = token / tbf_tick (default: %(default)ss)') parser.add_argument('--tbf-max-delay', type=int, default=optz['tbf_max_delay'], help='Maxmum amount of seconds, between message queue flush (default: %(default)ss)') parser.add_argument('--tbf-inc', type=int, default=optz['tbf_inc'], help='tbf_tick multiplier on consequent tbf overflow (default: %(default)s)') parser.add_argument('--tbf-dec', type=int, default=optz['tbf_dec'], help='tbf_tick divider on successful grab from non-empty bucket,' ' wont lower multiplier below 1 (default: %(default)s)') parser.add_argument('--img-w', type=int, default=optz["img_w"], help='max image icon width') parser.add_argument('--img-h', type=int, default=optz["img_h"], help='max image icon height') parser.add_argument('--debug', action='store_true', help='Enable debug logging to stderr.') optz = parser.parse_args() optz.filter_file = os.path.expanduser(optz.filter_file) core.Notification.default_timeout = optz.popup_timeout if optz.filter_test: func = core.get_filter(optz.filter_file) filtering_result = func(*optz.filter_test) msg_repr = 'Message - summary: {!r}, body: {!r}'.format(*optz.filter_test) print('{}\nFiltering result: {} ({})'.format( msg_repr, filtering_result, 'will pass' if filtering_result else "won't pass" )) sys.exit() import logging logging.basicConfig(level=logging.DEBUG if optz.debug else logging.WARNING) log = logging.getLogger() daemon = NotificationDaemon( bus, core.dbus_path, dbus.service.BusName(core.dbus_id, bus) ) loop = GObject.MainLoop() log.debug('Starting gobject loop') loop.run()
def main(argv=None): global optz, log import argparse def EnumAction(enum): class EnumAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, self.enum[values]) EnumAction.enum = enum return EnumAction parser = argparse.ArgumentParser(description='Desktop notification server.') parser.add_argument('--conf', metavar='path', help='Read option values from specified YAML configuration file.' ' Keys in subsectons (like "tbf.size") will be joined with' ' parent section name with dash (e.g. --tbf-size).' ' Any values specified on command line will override corresponding ones from file.' ' See also notification_thing.example.yaml file.') parser.add_argument('-f', '--no-fs-check', action='store_false', dest='fs_check', default=True, help='Dont queue messages if active window is fullscreen') parser.add_argument('-u', '--no-urgency-check', action='store_false', dest='urgency_check', default=True, help='Queue messages even if urgency is critical') parser.add_argument('-c', '--activity-timeout', type=int, default=int(optz['activity_timeout']), help='No-activity (dbus calls) timeout before closing the daemon instance' ' (less or equal zero - infinite, default: %(default)ss)') parser.add_argument('--no-status-notify', action='store_false', dest='status_notify', default=True, help='Do not send notification on changes in daemon settings.') parser.add_argument('--test-message', action='store_true', help='Issue test notification right after start.') parser.add_argument('--filter-file', default='~/.notification_filter', metavar='PATH', help='Read simple scheme rules for filtering notifications from file (default: %(default)s).') parser.add_argument('--filter-test', nargs=2, metavar=('SUMMARY', 'BODY'), help='Do not start daemon, just test given summary' ' and body against filter-file and print the result back to terminal.') parser.add_argument('--no-filter-sound', action='store_false', dest='filter_sound', default=True, help='Make sound calls in --filters-file scheme interpreter a no-op.' ' Only makes sense if sound calls in filters are actually used' ' and libcanberra is available, otherwise there wont be any sounds anyway.') parser.add_argument('-t', '--popup-timeout', type=int, default=int(optz['popup_timeout']*1000), help='Default timeout for notification popups removal (default: %(default)sms)') parser.add_argument('-q', '--queue-len', type=int, default=optz['queue_len'], help='How many messages should be queued on tbf overflow (default: %(default)s)') parser.add_argument('-s', '--history-len', type=int, default=optz['history_len'], help='How many last *displayed* messages to' ' remember to display again on demand (default: %(default)s)') parser.add_argument('--layout-anchor', choices=core.layout_anchor, action=EnumAction(core.layout_anchor), default=core.layout_anchor.top_left, help='Screen corner notifications gravitate to (default: top_left).') parser.add_argument('--layout-direction', choices=core.layout_direction, action=EnumAction(core.layout_direction), default=core.layout_direction.vertical, help='Direction for notification stack growth from --layout-anchor corner (default: vertical).') parser.add_argument('--layout-margin', default=3, help='Margin between notifications, screen edges, and some misc stuff (default: %(default)spx).') parser.add_argument('--icon-width', '--img-w', type=int, metavar='px', help='Scale icon (preserving aspect ratio) to width.') parser.add_argument('--icon-height', '--img-h', type=int, metavar='px', help='Scale icon (preserving aspect ratio) to height.') parser.add_argument('--markup-disable', action='store_true', help='Enable pango markup (tags, somewhat similar to html)' ' processing in all message summary/body parts by default.' ' These will either be rendered by pango' ' or stripped/shown-as-is (see --pango-markup-strip-on-err option), if invalid.' ' "x-nt-markup" bool hint can be used to' ' enable/disable markup on per-message basis, regardless of this option value.' ' See "Markup" section of the README for more details.') parser.add_argument('--markup-strip-on-err', action='store_true', help='Strip markup tags if pango fails to parse them' ' (when parsing markup is enabled) instead of rendering message with them as text.') parser.add_argument('--markup-warn-on-err', action='store_true', help='Issue loggin warning if passed markup tags cannot be parsed (when it is enabled).') parser.add_argument('--tbf-size', type=int, default=optz['tbf_size'], help='Token-bucket message-flow filter (tbf) bucket size (default: %(default)s)') parser.add_argument('--tbf-tick', type=int, default=optz['tbf_tick'], help='tbf update interval (new token), so token_inflow = token / tbf_tick (default: %(default)ss)') parser.add_argument('--tbf-max-delay', type=int, default=optz['tbf_max_delay'], help='Maxmum amount of seconds, between message queue flush (default: %(default)ss)') parser.add_argument('--tbf-inc', type=int, default=optz['tbf_inc'], help='tbf_tick multiplier on consequent tbf overflow (default: %(default)s)') parser.add_argument('--tbf-dec', type=int, default=optz['tbf_dec'], help='tbf_tick divider on successful grab from non-empty bucket,' ' wont lower multiplier below 1 (default: %(default)s)') parser.add_argument('--dbus-interface', default=optz['dbus_interface'], help='DBus interface to use (default: %(default)s)') parser.add_argument('--dbus-path', default=optz['dbus_path'], help='DBus object path to bind to (default: %(default)s)') parser.add_argument('--net-pub-bind', action='append', metavar='ip:port', help='Publish messages over network on a specified socket endpoint.' ' Can be either ip:port (assumed to be tcp socket,' ' e.g. 1.2.3.4:5678) or full zmq url (e.g. tcp://1.2.3.4:5678).' ' Can be specified multiple times.') parser.add_argument('--net-pub-connect', action='append', metavar='ip:port', help='Send published messages to specified subscriber socket (see --net-sub-bind).' ' Same format as for --net-pub-bind. Can be specified multiple times.') parser.add_argument('--net-sub-bind', action='append', metavar='ip:port', help='Create subscriber socket that' ' publishers can connect and send messages to (see --net-pub-connect).' ' Same format as for --net-pub-bind. Can be specified multiple times.') parser.add_argument('--net-sub-connect', action='append', metavar='ip:port', help='Receive published messages from a specified pub socket (see --net-pub-bind).' ' Same format as for --net-pub-bind. Can be specified multiple times.') parser.add_argument('--net-settings', metavar='yaml', help='Optional yaml/json encoded settings' ' for PubSub class init (e.g. hostname, buffer, reconnect_max, etc).') parser.add_argument('--debug', action='store_true', help='Enable debug logging to stderr.') args = argv or sys.argv[1:] optz = parser.parse_args(args) if optz.conf: import yaml for k, v in flatten_dict(yaml.safe_load(open(optz.conf)) or dict()): if v is None: continue k = '_'.join(k).replace('-', '_') if not hasattr(optz, k): parser.error('Unrecognized option in config file ({}): {}'.format(optz.conf, k)) setattr(optz, k, v) optz = parser.parse_args(args, optz) # re-parse to override cli-specified values import logging logging.basicConfig(level=logging.DEBUG if optz.debug else logging.WARNING) log = logging.getLogger('daemon') optz.filter_file = os.path.expanduser(optz.filter_file) core.Notification.default_timeout = optz.popup_timeout if optz.filter_sound: optz.filter_sound = core.get_sound_env( force_sync=optz.filter_test, trap_errors=not (optz.filter_test or optz.debug) ) if optz.filter_test: func = core.get_filter(optz.filter_file, optz.filter_sound) filtering_result = func(*optz.filter_test) msg_repr = 'Message - summary: {!r}, body: {!r}'.format(*optz.filter_test) print('{}\nFiltering result: {} ({})'.format( msg_repr, filtering_result, 'will pass' if filtering_result else "won't pass" )) sys.exit() DBusGMainLoop(set_as_default=True) bus = dbus.SessionBus() if optz.net_pub_bind or optz.net_pub_connect\ or optz.net_sub_bind or optz.net_sub_connect: if optz.net_settings and not isinstance(optz.net_settings, Mapping): import yaml optz.net_settings = yaml.safe_load(optz.net_settings) pubsub = PubSub(**(optz.net_settings or dict())) for addrs, call in [ (optz.net_pub_bind, pubsub.bind_pub), (optz.net_sub_bind, pubsub.bind_sub), (optz.net_pub_connect, pubsub.connect), (optz.net_sub_connect, pubsub.subscribe) ]: if isinstance(addrs, types.StringTypes): addrs = [addrs] for addr in set(addrs or set()): log.debug('zmq link: %s %s', call.im_func.func_name, addr) call(addr) else: pubsub = None daemon = notification_daemon_factory( pubsub, bus, optz.dbus_path, dbus.service.BusName(optz.dbus_interface, bus) ) loop = GLib.MainLoop() log.debug('Starting gobject loop') loop.run()