comparison main.c @ 13:eb97cafe34e5

Try to prevent messages from being chopped up into multiple notifications Read data into a buffer and only send a notification when 250ms have passed since the first read instead of sending a notification immediately after each read. This is an attempt to prevent messages from being chopped up into multiple notifications. A (rather negligible) downside of this approach is that multiple messages rapidly following each other are coalesced into a single notification.
author Guido Berhoerster <guido+xwrited@berhoerster.name>
date Mon, 16 Mar 2015 23:54:20 +0100
parents 6440ba6e3466
children 4a5330979433
comparison
equal deleted inserted replaced
12:6440ba6e3466 13:eb97cafe34e5
1 /* 1 /*
2 * Copyright (C) 2014 Guido Berhoerster <guido+xwrited@berhoerster.name> 2 * Copyright (C) 2015 Guido Berhoerster <guido+xwrited@berhoerster.name>
3 * 3 *
4 * Permission is hereby granted, free of charge, to any person obtaining 4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the 5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including 6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish, 7 * without limitation the rights to use, copy, modify, merge, publish,
38 #include <libnotify/notify.h> 38 #include <libnotify/notify.h>
39 #include "xwrited-debug.h" 39 #include "xwrited-debug.h"
40 #include "xwrited-unique.h" 40 #include "xwrited-unique.h"
41 #include "xwrited-utmp.h" 41 #include "xwrited-utmp.h"
42 42
43 #define BUFFER_TIMEOUT (250)
44
43 enum { 45 enum {
44 PIPE_R_FD = 0, 46 PIPE_R_FD = 0,
45 PIPE_W_FD 47 PIPE_W_FD
46 }; 48 };
47 49
48 static int signal_pipe_fd[2] = { -1, -1 }; 50 static int signal_pipe_fd[2] = { -1, -1 };
51 static guint notify_timeout_id;
52 static GMainLoop *loop;
53 static GString *buffer;
49 54
50 static void 55 static void
51 on_signal(int signo) 56 on_signal(int signo)
52 { 57 {
53 int old_errno = errno; 58 int old_errno = errno;
66 } 71 }
67 72
68 static gboolean 73 static gboolean
69 signal_read_cb(GIOChannel *source, GIOCondition cond, gpointer user_data) 74 signal_read_cb(GIOChannel *source, GIOCondition cond, gpointer user_data)
70 { 75 {
71 GMainLoop *loop = (GMainLoop *)user_data;
72 sigset_t sigset; 76 sigset_t sigset;
73 sigset_t old_sigset; 77 sigset_t old_sigset;
74 GIOStatus status; 78 GIOStatus status;
75 gsize n; 79 gsize n;
76 GError *error = NULL; 80 GError *error = NULL;
112 116
113 return (TRUE); 117 return (TRUE);
114 } 118 }
115 119
116 static gboolean 120 static gboolean
117 send_notification(GString *raw_str, GMainLoop *loop) 121 send_notification(void)
118 { 122 {
119 gboolean retval = FALSE; 123 gboolean retval = FALSE;
120 GString *utf8_str = NULL; 124 GString *utf8_str = NULL;
121 gchar *startp = raw_str->str; 125 gchar *startp = buffer->str;
122 gchar *endp; 126 gchar *endp;
123 GRegex *regex = NULL; 127 GRegex *regex = NULL;
124 GError *error = NULL; 128 GError *error = NULL;
125 gchar *body = NULL; 129 gchar *body = NULL;
126 GList *capabilities = NULL; 130 GList *capabilities = NULL;
127 gchar *tmp; 131 gchar *tmp;
128 NotifyNotification *notification = NULL; 132 NotifyNotification *notification = NULL;
129 133
130 utf8_str = g_string_sized_new(raw_str->len); 134 utf8_str = g_string_sized_new(buffer->len);
131 while (!g_utf8_validate(startp, raw_str->str + raw_str->len - 135 while (!g_utf8_validate(startp, buffer->str + buffer->len -
132 startp, (const gchar **)&endp)) { 136 startp, (const gchar **)&endp)) {
133 g_string_append_len(utf8_str, startp, endp - startp); 137 g_string_append_len(utf8_str, startp, endp - startp);
134 /* 138 /*
135 * replace each byte that does not belong to a UTF-8-encoded 139 * replace each byte that does not belong to a UTF-8-encoded
136 * character with the Unicode REPLACEMENT CHARACTER (U+FFFD) 140 * character with the Unicode REPLACEMENT CHARACTER (U+FFFD)
137 */ 141 */
138 g_string_append(utf8_str, "\357\277\275"); 142 g_string_append(utf8_str, "\357\277\275");
139 143
140 startp = endp + ((endp < raw_str->str + raw_str->len) ? 1 : 0); 144 startp = endp + ((endp < buffer->str + buffer->len) ? 1 : 0);
141 } 145 }
142 g_string_append_len(utf8_str, startp, raw_str->str + raw_str->len - 146 g_string_append_len(utf8_str, startp, buffer->str + buffer->len -
143 startp); 147 startp);
144 148
145 /* remove any CR, BEL and trailing space and tabs */ 149 /* remove any CR, BEL and trailing space and tabs */
146 regex = g_regex_new("([\r\a]+|[ \t\r\a]+$)", G_REGEX_MULTILINE, 0, 150 regex = g_regex_new("([\r\a]+|[ \t\r\a]+$)", G_REGEX_MULTILINE, 0,
147 &error); 151 &error);
210 g_regex_unref(regex); 214 g_regex_unref(regex);
211 } 215 }
212 if (utf8_str != NULL) { 216 if (utf8_str != NULL) {
213 g_string_free(utf8_str, TRUE); 217 g_string_free(utf8_str, TRUE);
214 } 218 }
219 /* prevent a permanently large buffer */
220 g_string_free(buffer, FALSE);
221 buffer = g_string_sized_new(BUFSIZ);
215 222
216 return (retval); 223 return (retval);
217 } 224 }
218 225
219 static gboolean 226 static gboolean
220 master_pty_read_cb(GIOChannel *source, GIOCondition cond, 227 notify_timeout_cb(gpointer user_data)
221 gpointer user_data)
222 { 228 {
223 GMainLoop *loop = (GMainLoop *)user_data; 229 if (!send_notification()) {
230 g_warning("failed to send notification");
231 }
232
233 notify_timeout_id = 0;
234
235 return (FALSE);
236 }
237
238 static gboolean
239 master_pty_read_cb(GIOChannel *source, GIOCondition cond, gpointer user_data)
240 {
224 gchar buf[BUFSIZ]; 241 gchar buf[BUFSIZ];
225 GString *raw_str = NULL;
226 GIOStatus status; 242 GIOStatus status;
227 gsize buf_len; 243 gsize buf_len;
228 GError *error = NULL; 244 GError *error = NULL;
229 245
230 if ((cond & G_IO_IN) || (cond & G_IO_PRI)) { 246 if ((cond & G_IO_IN) || (cond & G_IO_PRI)) {
231 raw_str = g_string_sized_new(BUFSIZ);
232 /* read message from master pty */ 247 /* read message from master pty */
233 while ((status = g_io_channel_read_chars(source, buf, BUFSIZ, 248 while ((status = g_io_channel_read_chars(source, buf, BUFSIZ,
234 &buf_len, &error)) == G_IO_STATUS_NORMAL) { 249 &buf_len, &error)) == G_IO_STATUS_NORMAL) {
235 if (buf_len > 0) { 250 if (buf_len > 0) {
236 g_debug("read %" G_GSSIZE_FORMAT " bytes from " 251 g_debug("read %" G_GSSIZE_FORMAT " bytes from "
237 "master pty", buf_len); 252 "master pty", buf_len);
238 g_string_append_len(raw_str, buf, 253 g_string_append_len(buffer, buf,
239 (gssize)buf_len); 254 (gssize)buf_len);
240 } 255 }
241 } 256 }
242 if (error != NULL) { 257 if (error != NULL) {
243 g_critical("failed to read from master pty: %s", 258 g_critical("failed to read from master pty: %s",
244 error->message); 259 error->message);
245 g_error_free(error); 260 g_error_free(error);
246 g_main_loop_quit(loop); 261 g_main_loop_quit(loop);
247 goto out; 262 return (FALSE);
248 } 263 }
249 264
250 if (!send_notification(raw_str, loop)) { 265 /*
251 g_warning("failed to send notification"); 266 * schedule a timeout for sending a notification with the
267 * buffered message
268 */
269 if (notify_timeout_id <= 0) {
270 notify_timeout_id = g_timeout_add(BUFFER_TIMEOUT,
271 notify_timeout_cb, NULL);
252 } 272 }
253 } 273 }
254 274
255 if ((cond & G_IO_ERR) || (cond & G_IO_HUP)) { 275 if ((cond & G_IO_ERR) || (cond & G_IO_HUP)) {
256 g_critical("connection to master pty broken"); 276 g_critical("connection to master pty broken");
257 g_main_loop_quit(loop); 277 g_main_loop_quit(loop);
258 goto out; 278 return (FALSE);
259 }
260
261 out:
262 if (raw_str != NULL) {
263 g_string_free(raw_str, TRUE);
264 } 279 }
265 280
266 return (TRUE); 281 return (TRUE);
267 } 282 }
268 283
269 int 284 int
270 main(int argc, char *argv[]) 285 main(int argc, char *argv[])
271 { 286 {
272 int status = EXIT_FAILURE; 287 int status = EXIT_FAILURE;
273 GMainLoop *loop = NULL;
274 GError *error = NULL; 288 GError *error = NULL;
275 XWritedUnique *app = NULL; 289 XWritedUnique *app = NULL;
276 GOptionContext *context = NULL; 290 GOptionContext *context = NULL;
277 struct sigaction sigact; 291 struct sigaction sigact;
278 GIOChannel *signal_channel = NULL; 292 GIOChannel *signal_channel = NULL;
338 if (loop == NULL) { 352 if (loop == NULL) {
339 g_critical("failed to create main loop"); 353 g_critical("failed to create main loop");
340 goto out; 354 goto out;
341 } 355 }
342 356
357 buffer = g_string_sized_new(BUFSIZ);
358
343 /* open master pty */ 359 /* open master pty */
344 masterfd = posix_openpt(O_RDWR | O_NOCTTY); 360 masterfd = posix_openpt(O_RDWR | O_NOCTTY);
345 if (masterfd == -1) { 361 if (masterfd == -1) {
346 g_critical("failed to open master pty: %s", g_strerror(errno)); 362 g_critical("failed to open master pty: %s", g_strerror(errno));
347 goto out; 363 goto out;
378 error->message); 394 error->message);
379 g_error_free(error); 395 g_error_free(error);
380 goto out; 396 goto out;
381 } 397 }
382 if (!g_io_add_watch(master_pty_channel, G_IO_IN | G_IO_PRI | G_IO_HUP | 398 if (!g_io_add_watch(master_pty_channel, G_IO_IN | G_IO_PRI | G_IO_HUP |
383 G_IO_ERR, master_pty_read_cb, loop)) { 399 G_IO_ERR, master_pty_read_cb, NULL)) {
384 g_critical("failed to add watch on signal channel"); 400 g_critical("failed to add watch on signal channel");
385 goto out; 401 goto out;
386 } 402 }
387 403
388 /* create pipe for delivering signals to a listener in the main loop */ 404 /* create pipe for delivering signals to a listener in the main loop */
415 error->message); 431 error->message);
416 g_error_free(error); 432 g_error_free(error);
417 goto out; 433 goto out;
418 } 434 }
419 if (g_io_add_watch(signal_channel, G_IO_IN | G_IO_PRI | G_IO_HUP | 435 if (g_io_add_watch(signal_channel, G_IO_IN | G_IO_PRI | G_IO_HUP |
420 G_IO_ERR, signal_read_cb, loop) == 0) { 436 G_IO_ERR, signal_read_cb, NULL) == 0) {
421 g_critical("failed to add watch on the signal channel"); 437 g_critical("failed to add watch on the signal channel");
422 goto out; 438 goto out;
423 } 439 }
424 440
425 /* set up signal handler */ 441 /* set up signal handler */
471 487
472 if (masterfd != -1) { 488 if (masterfd != -1) {
473 close(masterfd); 489 close(masterfd);
474 } 490 }
475 491
492 if (buffer != NULL) {
493 g_string_free(buffer, FALSE);
494 }
495
476 if (app != NULL) { 496 if (app != NULL) {
477 g_object_unref(app); 497 g_object_unref(app);
478 } 498 }
479 499
480 if (loop != NULL) { 500 if (loop != NULL) {