Пример #1
0
	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
Пример #2
0
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()
Пример #3
0
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()
Пример #4
0
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()
Пример #5
0
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()
Пример #6
0
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()