Release new version 2.13.0
[grml-scripts.git] / usr_bin / notifyd.py
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3 # Copyright (C) 2005-2009 Alexander Bernauer <alex@copton.net>
4 # Copyright (C) 2005-2009 Rico Schiekel <fire@downgra.de>
5 # Copyright (C) 2005-2009 Ulrich Dangel <uli@spamt.net>
6 #
7 # This program is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License
9 # as published by the Free Software Foundation version 2
10 # of the License.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA # 02111-1307, USA.
20
21
22 """
23 example of ~/.notifyd.conf:
24 ---------------------------
25
26 import os
27
28 host = "127.0.0.1"
29 port = 8901
30 logfile = os.path.expanduser("~/.event.log")
31 osdparams = "-p bottom  --color=red --delay=4 --age=4 " \
32  "--font=\-\*\-helvetica\-medium\-r\-\*\-\*\-34\-\*\-\*\-\*\-\*\-\*\-\*\-15 " \  # noqa: W605,E501
33      "--offset=100 --shadow=0 --lines=5 --align=right --indent=100"
34 actions = (
35     (".*", [log], True),
36     ("IRC:&bitlbee:bitlbee", [], False),
37     (".*shutdown.*", [command('sudo shutdown -h now %(msg)s')], False),
38     (".*", [libnotify], False),
39 )
40
41 """
42
43 import getopt
44 import logging
45 import os
46 import re
47 import socket
48 import string
49 import subprocess
50 import sys
51
52 default_hostname = "localhost"
53 default_port = 1234
54 default_osd_params = osdparams = (
55     "-p bottom  --color=red --delay=4 --age=4 "
56     "--font=\-\*\-helvetica\-medium\-r\-\*\-\*\-34\-\*\-\*\-\*\-\*\-\*\-\*\-15 "  # noqa: W605,E501
57     "--offset=100 --shadow=0 --lines=5 --align=right --indent=100"
58 )
59 default_logfile = None
60
61
62 def play(sound_file):
63     def play_wrapper(msg):
64         with open(os.devnull, "w") as devnull:
65             subprocess.Popen(["/usr/bin/aplay", sound_file], stderr=devnull)
66
67     return play_wrapper
68
69
70 def execute(command):
71     def command_wrapper(msg):
72         subprocess.call(command % dict(msg=msg))
73
74     return command_wrapper
75
76
77 def osd(msg):
78     osdcmd = "/usr/bin/osd_cat"
79     osdpipe = os.popen("%s %s" % (osdcmd, osdparams), "w")
80     osdpipe.write(msg)
81     osdpipe.close()
82
83
84 def libnotify(msg):
85     try:
86         import dbus
87     except ImportError:
88         sys.stderr.write("Please install python-dbus\n")
89         raise SystemExit(1)
90
91     bus = dbus.SessionBus()
92     notifyService = bus.get_object(
93         "org.freedesktop.Notifications", "/org/freedesktop/Notifications"
94     )
95     interface = dbus.Interface(notifyService, "org.freedesktop.Notifications")
96
97     message, title = (":" + msg).split(":")[::-1][0:2]
98     if not title:
99         title, message = message, title
100     interface.Notify(
101         "notify-server",
102         0,
103         "notification-message-im",
104         title,
105         message,
106         [],
107         {"x-canonical-append": "allowed"},
108         -1,
109     )
110
111
112 def log(msg):
113     if logger:
114         logger.info(msg)
115
116
117 def syntax():
118     print("osd_server.py [options]")
119     print("   options:")
120     print("     -h --help       print this message")
121     print("     -H --host       host of the osd server (def: " + default_hostname + ")")
122     print(
123         "     -P --port       port of the osd server (def: " + str(default_port) + ")"
124     )
125     print("     -l --log        log file ('-' logs to stdout)")
126
127
128 env = {
129     "play": play,
130     "execute": execute,
131     "osd": osd,
132     "libnotify": libnotify,
133     "log": log,
134     "host": default_hostname,
135     "port": default_port,
136     "logfile": default_logfile,
137 }
138
139 default_actions = (
140     (".*", [log], True),
141     (".*", [libnotify], False),
142 )
143
144
145 default_bind = (default_hostname, default_port)
146
147 try:
148     exec(
149         compile(
150             open(os.path.expanduser("~/.notifyd.conf"), "rb").read(),
151             os.path.expanduser("~/.notifyd.conf"),
152             "exec",
153         ),
154         {},
155         env,
156     )
157 except IOError:
158     pass
159
160 try:
161     opts, args = getopt.getopt(
162         sys.argv[1:], "hH:P:l:", ["help", "host=", "port=", "log="]
163     )
164 except getopt.GetoptError:
165     syntax()
166     sys.exit(2)
167
168 for opt, arg in opts:
169     if opt in ("-h", "--help"):
170         syntax()
171         sys.exit(3)
172     elif opt in ("-H", "--host"):
173         env["host"] = arg
174     elif opt in ("-P", "--port"):
175         env["port"] = int(arg)
176     elif opt in ("-l", "--log"):
177         env["logfile"] = arg
178
179
180 actions = env.get("actions", default_actions)
181 logfile_name = env.get("logfile")
182 logfile_format = env.get("logformat", "%(asctime)s %(message)s")
183 bind_address = (env["host"], env["port"])
184 osd_params = env.get("osdparams", default_osd_params)
185
186 if logfile_name:
187     logger = logging.getLogger("notify_server")
188     lformatter = logging.Formatter(logfile_format)
189     if logfile_name not in ("", "-"):
190         lfh = logging.FileHandler(logfile_name)
191         lfh.setFormatter(lformatter)
192         logger.addHandler(lfh)
193     else:
194         lout = logging.StreamHandler(sys.stdout)
195         lout.setFormatter(lformatter)
196         logger.addHandler(lout)
197     logger.setLevel(logging.INFO)
198 else:
199     logger = None
200
201 listen = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
202 listen.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
203 listen.bind(bind_address)
204 listen.listen(5)
205
206
207 def filter_char(c):
208     return c in string.printable + "äöüßÄÖÜ" and c or "_"
209
210
211 while 1:
212     try:
213         (con, addr) = listen.accept()
214     except OSError:
215         continue
216     data = con.recv(50).strip()
217     con.close()
218
219     log = "".join(filter_char(c) for c in data)
220
221     for pattern, handlers, cont in actions:
222         if re.match(pattern, log):
223             for handler in handlers:
224                 handler(log)
225             if not cont:
226                 break