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 }