Mercurial > projects > xwrited
comparison main.c @ 0:52694b49dcc4
Initial revision
author | Guido Berhoerster <guido+xwrited@berhoerster.name> |
---|---|
date | Sun, 27 Apr 2014 23:07:51 +0200 |
parents | |
children | 0907cc7064d4 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:52694b49dcc4 |
---|---|
1 /* | |
2 * Copyright (C) 2011 Guido Berhoerster <guido+xwrited@berhoerster.name> | |
3 * | |
4 * Permission is hereby granted, free of charge, to any person obtaining | |
5 * a copy of this software and associated documentation files (the | |
6 * "Software"), to deal in the Software without restriction, including | |
7 * without limitation the rights to use, copy, modify, merge, publish, | |
8 * distribute, sublicense, and/or sell copies of the Software, and to | |
9 * permit persons to whom the Software is furnished to do so, subject to | |
10 * the following conditions: | |
11 * | |
12 * The above copyright notice and this permission notice shall be included | |
13 * in all copies or substantial portions of the Software. | |
14 * | |
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
19 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
20 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
21 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
22 */ | |
23 | |
24 #define _XOPEN_SOURCE 600 | |
25 | |
26 #include <stdio.h> | |
27 #include <stdlib.h> | |
28 #include <string.h> | |
29 #include <unistd.h> | |
30 #include <fcntl.h> | |
31 #include <sys/stat.h> | |
32 #include <signal.h> | |
33 #include <errno.h> | |
34 #include <locale.h> | |
35 #include <libintl.h> | |
36 #include <glib.h> | |
37 #include <glib/gi18n.h> | |
38 #include <libnotify/notify.h> | |
39 #include "xwrited-debug.h" | |
40 #include "xwrited-unique.h" | |
41 #include "xwrited-utmp.h" | |
42 | |
43 enum { | |
44 PIPE_R_FD = 0, | |
45 PIPE_W_FD | |
46 }; | |
47 | |
48 static int signal_pipe_fd[2] = { -1, -1 }; | |
49 | |
50 static void | |
51 on_signal(int signo) | |
52 { | |
53 int old_errno = errno; | |
54 ssize_t n; | |
55 sigset_t sigset; | |
56 | |
57 /* try to read unread signals from the pipe and add the new one to it */ | |
58 n = read(signal_pipe_fd[PIPE_R_FD], &sigset, sizeof (sigset)); | |
59 if ((n == -1) || ((size_t)n < sizeof (sigset))) { | |
60 sigemptyset(&sigset); | |
61 } | |
62 sigaddset(&sigset, signo); | |
63 write(signal_pipe_fd[PIPE_W_FD], &sigset, sizeof (sigset)); | |
64 | |
65 errno = old_errno; | |
66 } | |
67 | |
68 static gboolean | |
69 signal_read_cb(GIOChannel *source, GIOCondition cond, gpointer user_data) | |
70 { | |
71 GMainLoop *loop = (GMainLoop *)user_data; | |
72 sigset_t sigset; | |
73 sigset_t old_sigset; | |
74 GIOStatus status; | |
75 gsize n; | |
76 GError *error = NULL; | |
77 | |
78 /* | |
79 * deal with pending signals previously received in the signal handler, | |
80 * try to read a sigset from the pipe, avoid partial reads by blocking | |
81 * all signals during the read operation | |
82 */ | |
83 sigfillset(&sigset); | |
84 sigprocmask(SIG_BLOCK, &sigset, &old_sigset); | |
85 status = g_io_channel_read_chars(source, (gchar *)&sigset, | |
86 sizeof (sigset), &n, &error); | |
87 sigprocmask(SIG_SETMASK, &old_sigset, NULL); | |
88 if (status != G_IO_STATUS_NORMAL) { | |
89 if (status != G_IO_STATUS_AGAIN) { | |
90 if (error != NULL) { | |
91 g_critical("failed to read from signal pipe: " | |
92 "%s", error->message); | |
93 g_error_free(error); | |
94 g_main_loop_quit(loop); | |
95 } else { | |
96 g_critical("failed to read from signal pipe"); | |
97 g_main_loop_quit(loop); | |
98 } | |
99 } | |
100 } else if (n != sizeof (sigset)) { | |
101 g_critical("short read from signal pipe"); | |
102 g_main_loop_quit(loop); | |
103 } else { | |
104 if ((sigismember(&sigset, SIGINT) == 1) || | |
105 (sigismember(&sigset, SIGTERM) == 1) || | |
106 (sigismember(&sigset, SIGQUIT) == 1) || | |
107 (sigismember(&sigset, SIGHUP) == 1)) { | |
108 g_debug("received signal, exiting"); | |
109 g_main_loop_quit(loop); | |
110 } | |
111 } | |
112 | |
113 return (TRUE); | |
114 } | |
115 | |
116 static gboolean | |
117 send_notification(GString *raw_str, GMainLoop *loop) | |
118 { | |
119 gboolean retval = FALSE; | |
120 GString *utf8_str = NULL; | |
121 gchar *startp = raw_str->str; | |
122 gchar *endp; | |
123 GRegex *regex = NULL; | |
124 GError *error = NULL; | |
125 gchar *body = NULL; | |
126 GList *capabilities = NULL; | |
127 gchar *tmp; | |
128 NotifyNotification *notification = NULL; | |
129 | |
130 utf8_str = g_string_sized_new(raw_str->len); | |
131 while (!g_utf8_validate(startp, raw_str->str + raw_str->len - | |
132 startp, (const gchar **)&endp)) { | |
133 g_string_append_len(utf8_str, startp, endp - startp); | |
134 /* | |
135 * replace each byte that does not belong to a UTF-8-encoded | |
136 * character with the Unicode REPLACEMENT CHARACTER (U+FFFD) | |
137 */ | |
138 g_string_append(utf8_str, "\357\277\275"); | |
139 | |
140 startp = endp + ((endp < raw_str->str + raw_str->len) ? 1 : 0); | |
141 } | |
142 g_string_append_len(utf8_str, startp, raw_str->str + raw_str->len - | |
143 startp); | |
144 | |
145 /* remove any CR, BEL and trailing space and tabs */ | |
146 regex = g_regex_new("([\r\a]+|[ \t\r\a]+$)", G_REGEX_MULTILINE, 0, | |
147 &error); | |
148 if (error != NULL) { | |
149 goto out; | |
150 } | |
151 body = g_regex_replace_literal(regex, utf8_str->str, -1, 0, "", 0, | |
152 &error); | |
153 if (error != NULL) { | |
154 goto out; | |
155 } | |
156 | |
157 /* | |
158 * skip empty messages or messages only consisting of whitespace and | |
159 * control characters | |
160 */ | |
161 if ((strlen(body) == 0) || | |
162 !g_regex_match_simple("[^[:space:][:cntrl:]]", body, 0, 0)) { | |
163 goto out; | |
164 } | |
165 | |
166 /* | |
167 * if the notification daemon supports markup the message needs to be | |
168 * escaped | |
169 */ | |
170 capabilities = notify_get_server_caps(); | |
171 if (g_list_find_custom(capabilities, "body-markup", | |
172 (GCompareFunc)strcmp) != NULL) { | |
173 tmp = g_markup_escape_text(body, -1); | |
174 g_free(body); | |
175 body = tmp; | |
176 } | |
177 | |
178 /* show notification */ | |
179 notification = notify_notification_new(_("Message received"), | |
180 body, "utilities-terminal" | |
181 #if !defined(NOTIFY_VERSION_MINOR) || \ | |
182 (NOTIFY_VERSION_MAJOR == 0 && NOTIFY_VERSION_MINOR < 7) | |
183 , NULL | |
184 #endif | |
185 ); | |
186 if (notification == NULL) { | |
187 g_critical("failed to create a notification object"); | |
188 g_main_loop_quit(loop); | |
189 goto out; | |
190 } | |
191 notify_notification_set_timeout(notification, NOTIFY_EXPIRES_NEVER); | |
192 retval = notify_notification_show(notification, NULL); | |
193 | |
194 out: | |
195 if (notification != NULL) { | |
196 g_object_unref(G_OBJECT(notification)); | |
197 } | |
198 if (capabilities != NULL) { | |
199 g_list_free_full(capabilities, g_free); | |
200 } | |
201 g_free(body); | |
202 if (regex != NULL) { | |
203 g_regex_unref(regex); | |
204 } | |
205 if (utf8_str != NULL) { | |
206 g_string_free(utf8_str, TRUE); | |
207 } | |
208 | |
209 return (retval); | |
210 } | |
211 | |
212 static gboolean | |
213 master_pty_read_cb(GIOChannel *source, GIOCondition cond, | |
214 gpointer user_data) | |
215 { | |
216 GMainLoop *loop = (GMainLoop *)user_data; | |
217 gchar buf[BUFSIZ]; | |
218 GString *raw_str = NULL; | |
219 GIOStatus status; | |
220 gsize buf_len; | |
221 GError *error = NULL; | |
222 | |
223 if ((cond & G_IO_IN) || (cond & G_IO_PRI)) { | |
224 raw_str = g_string_sized_new(BUFSIZ); | |
225 /* read message from master pty */ | |
226 while ((status = g_io_channel_read_chars(source, buf, BUFSIZ, | |
227 &buf_len, &error)) == G_IO_STATUS_NORMAL) { | |
228 if (buf_len > 0) { | |
229 g_debug("read %" G_GSSIZE_FORMAT " bytes from " | |
230 "master pty", buf_len); | |
231 g_string_append_len(raw_str, buf, | |
232 (gssize)buf_len); | |
233 } | |
234 } | |
235 if (error != NULL) { | |
236 g_critical("failed to read from master pty: %s", | |
237 error->message); | |
238 g_error_free(error); | |
239 g_main_loop_quit(loop); | |
240 goto out; | |
241 } | |
242 | |
243 if (!send_notification(raw_str, loop)) { | |
244 g_warning("failed to send notification"); | |
245 } | |
246 } | |
247 | |
248 if ((cond & G_IO_ERR) || (cond & G_IO_HUP)) { | |
249 g_critical("connection to master pty broken"); | |
250 g_main_loop_quit(loop); | |
251 goto out; | |
252 } | |
253 | |
254 out: | |
255 if (raw_str != NULL) { | |
256 g_string_free(raw_str, TRUE); | |
257 } | |
258 | |
259 return (TRUE); | |
260 } | |
261 | |
262 int | |
263 main(int argc, char *argv[]) | |
264 { | |
265 int status = EXIT_FAILURE; | |
266 GMainLoop *loop = NULL; | |
267 GError *error = NULL; | |
268 XWritedUnique *app = NULL; | |
269 GOptionContext *context = NULL; | |
270 struct sigaction sigact; | |
271 GIOChannel *signal_channel = NULL; | |
272 GIOChannel *master_pty_channel = NULL; | |
273 int masterfd = -1; | |
274 int slavefd = -1; | |
275 char *slave_name = NULL; | |
276 gboolean vflag = FALSE; | |
277 gboolean dflag = FALSE; | |
278 const GOptionEntry options[] = { | |
279 { "debug", 'd', 0, G_OPTION_ARG_NONE, &dflag, | |
280 N_("Show extra debugging information"), NULL }, | |
281 { "version", 'V', 0, G_OPTION_ARG_NONE, &vflag, | |
282 N_("Print the current version and exit"), NULL }, | |
283 { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, 0 } | |
284 }; | |
285 | |
286 setlocale(LC_ALL, ""); | |
287 bindtextdomain(PACKAGE, LOCALEDIR); | |
288 bind_textdomain_codeset(PACKAGE, "UTF-8"); | |
289 textdomain(PACKAGE); | |
290 | |
291 g_type_init(); | |
292 | |
293 context = g_option_context_new("- display write and wall messages as " | |
294 "desktop notifications"); | |
295 g_option_context_add_main_entries(context, options, PACKAGE); | |
296 g_option_context_set_translation_domain(context, PACKAGE); | |
297 g_option_context_parse(context, &argc, &argv, &error); | |
298 if (error != NULL) { | |
299 g_printerr("%s.\n", error->message); | |
300 g_error_free(error); | |
301 goto out; | |
302 } | |
303 | |
304 xwrited_debug_init(dflag); | |
305 | |
306 if (vflag) { | |
307 g_print("%s %s\n", PACKAGE, VERSION); | |
308 status = EXIT_SUCCESS; | |
309 goto out; | |
310 } | |
311 | |
312 app = xwrited_unique_new("org.guido-berhoerster.code.xwrited"); | |
313 if (app == NULL) { | |
314 g_critical("failed to initialize application"); | |
315 goto out; | |
316 } | |
317 if (!xwrited_unique_is_unique(app)) { | |
318 g_printerr(_("xwrited is already running in this session.\n")); | |
319 goto out; | |
320 } | |
321 | |
322 if (!notify_init(APP_NAME)) { | |
323 g_critical("failed to initialize libnotify"); | |
324 goto out; | |
325 } | |
326 | |
327 loop = g_main_loop_new(NULL, FALSE); | |
328 if (loop == NULL) { | |
329 g_critical("failed to create main loop"); | |
330 goto out; | |
331 } | |
332 | |
333 /* open master pty */ | |
334 masterfd = posix_openpt(O_RDWR | O_NOCTTY); | |
335 if (masterfd == -1) { | |
336 g_critical("failed to open master pty: %s", g_strerror(errno)); | |
337 goto out; | |
338 } | |
339 | |
340 /* create slave pty */ | |
341 if ((grantpt(masterfd) == -1) || (unlockpt(masterfd) == -1)) { | |
342 g_critical("failed to create slave pty: %s", g_strerror(errno)); | |
343 goto out; | |
344 } | |
345 slave_name = ptsname(masterfd); | |
346 if (slave_name == NULL) { | |
347 g_critical("failed to obtain name of slave pty"); | |
348 goto out; | |
349 } | |
350 | |
351 /* | |
352 * keep an open fd around order to prevent closing the master fd when | |
353 * receiving an EOF | |
354 */ | |
355 slavefd = open(slave_name, O_RDWR); | |
356 if (slavefd == -1) { | |
357 g_critical("failed to open slave pty: %s", g_strerror(errno)); | |
358 goto out; | |
359 } | |
360 | |
361 /* create a GIOChannel for monitoring the master pty for messages */ | |
362 master_pty_channel = g_io_channel_unix_new(masterfd); | |
363 g_io_channel_set_flags(master_pty_channel, | |
364 g_io_channel_get_flags(master_pty_channel) | G_IO_FLAG_NONBLOCK, | |
365 &error); | |
366 if (error != NULL) { | |
367 g_critical("failed set flags on the master pty channel: %s", | |
368 error->message); | |
369 g_error_free(error); | |
370 goto out; | |
371 } | |
372 if (!g_io_add_watch(master_pty_channel, G_IO_IN | G_IO_PRI | G_IO_HUP | | |
373 G_IO_ERR, master_pty_read_cb, loop)) { | |
374 g_critical("failed to add watch on signal channel"); | |
375 goto out; | |
376 } | |
377 | |
378 /* create pipe for delivering signals to a listener in the main loop */ | |
379 if (pipe(signal_pipe_fd) == -1) { | |
380 g_critical("failed to create signal pipe: %s", | |
381 g_strerror(errno)); | |
382 goto out; | |
383 } | |
384 if (fcntl(signal_pipe_fd[PIPE_W_FD], F_SETFL, O_NONBLOCK) == -1) { | |
385 g_critical("failed to set flags on signal pipe: %s", | |
386 g_strerror(errno)); | |
387 goto out; | |
388 } | |
389 | |
390 /* create GIO channel for reading from the signal_pipe */ | |
391 signal_channel = g_io_channel_unix_new(signal_pipe_fd[PIPE_R_FD]); | |
392 g_io_channel_set_encoding(signal_channel, NULL, &error); | |
393 if (error != NULL) { | |
394 g_critical("failed to set binary encoding for signal channel: " | |
395 "%s", error->message); | |
396 g_error_free(error); | |
397 goto out; | |
398 } | |
399 g_io_channel_set_buffered(signal_channel, FALSE); | |
400 g_io_channel_set_flags(signal_channel, | |
401 g_io_channel_get_flags(signal_channel) | G_IO_FLAG_NONBLOCK, | |
402 &error); | |
403 if (error != NULL) { | |
404 g_critical("failed set flags on signal channel: %s", | |
405 error->message); | |
406 g_error_free(error); | |
407 goto out; | |
408 } | |
409 if (g_io_add_watch(signal_channel, G_IO_IN | G_IO_PRI | G_IO_HUP | | |
410 G_IO_ERR, signal_read_cb, loop) == 0) { | |
411 g_critical("failed to add watch on the signal channel"); | |
412 goto out; | |
413 } | |
414 | |
415 /* set up signal handler */ | |
416 sigact.sa_handler = on_signal; | |
417 sigact.sa_flags = SA_RESTART; | |
418 sigemptyset(&sigact.sa_mask); | |
419 if ((sigaction(SIGINT, &sigact, NULL) < 0) || | |
420 (sigaction(SIGTERM, &sigact, NULL) < 0) || | |
421 (sigaction(SIGQUIT, &sigact, NULL) < 0) || | |
422 (sigaction(SIGHUP, &sigact, NULL) < 0)) { | |
423 g_critical("failed to set up signal handler"); | |
424 goto out; | |
425 } | |
426 | |
427 xwrited_utmp_add_entry(masterfd); | |
428 | |
429 /* main loop */ | |
430 g_main_loop_run(loop); | |
431 | |
432 xwrited_utmp_remove_entry(masterfd); | |
433 | |
434 status = EXIT_SUCCESS; | |
435 | |
436 out: | |
437 if (context != NULL) { | |
438 g_option_context_free(context); | |
439 } | |
440 | |
441 if (signal_channel != NULL) { | |
442 g_io_channel_shutdown(signal_channel, FALSE, NULL); | |
443 g_io_channel_unref(signal_channel); | |
444 } | |
445 | |
446 if (signal_pipe_fd[PIPE_R_FD] != -1) { | |
447 close(signal_pipe_fd[PIPE_R_FD]); | |
448 } | |
449 if (signal_pipe_fd[PIPE_W_FD] != -1) { | |
450 close(signal_pipe_fd[PIPE_W_FD]); | |
451 } | |
452 | |
453 if (master_pty_channel != NULL) { | |
454 g_io_channel_shutdown(master_pty_channel, FALSE, NULL); | |
455 g_io_channel_unref(master_pty_channel); | |
456 } | |
457 | |
458 if (slavefd != -1) { | |
459 close(slavefd); | |
460 } | |
461 | |
462 if (masterfd != -1) { | |
463 close(masterfd); | |
464 } | |
465 | |
466 if (app != NULL) { | |
467 g_object_unref(app); | |
468 } | |
469 | |
470 if (loop != NULL) { | |
471 g_main_loop_unref(loop); | |
472 } | |
473 | |
474 if (notify_is_initted()) { | |
475 notify_uninit(); | |
476 } | |
477 | |
478 exit(status); | |
479 } |